Computational proteomics of a THP-1 human leukaemia cell line

1 Abstract

This vignette contains the R1 pipeline used for the computational analysis reported in the paper “Spatiotemporal proteomic profiling of the pro-inflammatory response to lipopolysaccharide in the THP-1 human leukemia cell line” by Mulvey, Breckels et al.2

2 The data

In this vignette we work with the data at the PSM and protein level which which are freely and openly available as part of the Bioconductor pRolocdata package (≥v1.27.3)3 and in the supplementary information as disseminated with the accompanying manuscript.2

All analysis in the manuscript is done in the R programming language, unless otherwise stated, using the suite of mass spectrometry spatial proteomics packages MSnbase???, pRoloc3, pRolocGUI and pRolocdata.

All raw mass spectrometry proteomics data from this study have been deposited to the ProteomeXchange Consortium via the PRIDE partner repository,4 with the dataset identifier PXD023509.

2.1 Accessing the data in R

The pRolocdata package is a R Bioconductor experiment package that collects mass spectrometry-based spatial/organelle and protein-complex datasets from published experiments. Each data is stored in a container called a MSnSet instance (see the MSnbase for details) which allows users to work seamlessly with the R pRoloc and pRolocGUI software for spatial proteomics data analysis and visualisation.

The first step is to install the pRoloc and pRolocdata packages from Bioconductor

if (!requireNamespace("BiocManager", quietly = TRUE))
    install.packages("BiocManager")
BiocManager::install("pRoloc", "pRolocdata")

## we also use the knitr and VennDiagram, ggplot2 packages in this 
## vignette so please  install if you do not have them 
BiocManager::install(c("knitr", "VennDiagram", "ggplot2", "dplyr", "kableExtra", 
                       "coda", "reshape2", "circlize", "ggalluvial", "tidyr", 
                       "Rmisc", "gridExtra", "gplots", "scico", "plotrix",
                       "RColorBrewer"))

Note: To access the widest selection on proteomics datasets we advise users to install the latest version pRolocdata package from Bioconductor. The THP data is in version >=1.29.1

Now we can load the packages by calling library function in R

library("pRoloc")
library("pRolocdata")

and subsequently we can now access all the datasets in pRolocdata. To list all available datasets we use the data function.

data(package = "pRolocdata")

We see there are 27 datasets (including the PSM and protein level data) associated with the manuscript [1], 20 from the hyperLOPIT data anlysis and 7 from the LPS timecourse.2 For more information on these datasets, we can type

?thpLOPIT_lps_mulvey2021
?lpsTimecourse_mulvey2021

Figure 1. Screenshot of the documentation files in R for the THP LOPIT and LPS timecourse datasets

Each dataset can be loaded using the data function. For example, to load the final protein level LOPIT datasets for the unstimulated and 12 hour LPS stimulated datasets,

data("thpLOPIT_unstimulated_mulvey2021")
thpLOPIT_unstimulated_mulvey2021

data("thpLOPIT_lps_mulvey2021")
thpLOPIT_lps_mulvey2021

In the next sections we introduce the data, experimental design and brief overview of the experimental protocol before walking users through the downstream analysis which was used to generate the datasets available in pRolocdata and the manuscript Mulvey et al 2021.2

3 Temporal proteomics data analysis

3.1 Summary

We assessed the global proteomic landscape of the THP-1 monocytic leukaemic cell line using a shotgun proteomics time course. Total (unfractionated) cell lysates were collected at 0, 2, 4, 6, 12, and 24 hour time-points following LPS stimulation and processed for quantitative, MS-based proteomics analysis. We reproducibly quantified 4,292 proteins across 3 biological replicate experiments, at all time-points. In total 311 proteins were found to be altered in abundance during the time course of LPS stimulation, with statistical significance (adjusted p-value < 0.01, log_2 fold-change > 0.6). The results are reported in full in Mulvey et al. 2021.2

3.2 PSM level data processing

The 3 PSM level datasets were imported into R and missing values were carefully assessed.

We can load the PSM and protein level data from the pRolocdata package by using the data function.

## Unstimulated data
data("psms_lpsTimecourse_rep1_mulvey2021")
data("psms_lpsTimecourse_rep2_mulvey2021")
data("psms_lpsTimecourse_rep3_mulvey2021")

psms_lpsTimecourse_rep1_mulvey2021
## MSnSet (storageMode: lockedEnvironment)
## assayData: 34640 features, 6 samples 
##   element names: exprs 
## protocolData: none
## phenoData
##   sampleNames: 0 hr rep 1 2 hr rep 1 ... 24 hr rep 1 (6 total)
##   varLabels: Tag Time Replicate
##   varMetadata: labelDescription
## featureData
##   featureNames: 1 2 ... 34640 (34640 total)
##   fvarLabels: Checked Confidence ... PSM.count (42 total)
##   fvarMetadata: labelDescription
## experimentData: use 'experimentData(object)'
## Annotation:  
## - - - Processing information - - -
## Subset [218958,6][34640,6] Tue Jan 12 14:42:27 2021 
##  MSnbase version: 2.14.2
dim(psms_lpsTimecourse_rep1_mulvey2021)
## [1] 34640     6
psms_lpsTimecourse_rep2_mulvey2021
## MSnSet (storageMode: lockedEnvironment)
## assayData: 49806 features, 6 samples 
##   element names: exprs 
## protocolData: none
## phenoData
##   sampleNames: 0 hr rep 2 2 hr rep 2 ... 24 hr rep 2 (6 total)
##   varLabels: Tag Time Replicate
##   varMetadata: labelDescription
## featureData
##   featureNames: 34641 34642 ... 84446 (49806 total)
##   fvarLabels: Checked Confidence ... PSM.count (42 total)
##   fvarMetadata: labelDescription
## experimentData: use 'experimentData(object)'
## Annotation:  
## - - - Processing information - - -
## Subset [218958,6][49806,6] Tue Jan 12 14:42:27 2021 
##  MSnbase version: 2.14.2
dim(psms_lpsTimecourse_rep2_mulvey2021)
## [1] 49806     6
psms_lpsTimecourse_rep3_mulvey2021
## MSnSet (storageMode: lockedEnvironment)
## assayData: 41853 features, 6 samples 
##   element names: exprs 
## protocolData: none
## phenoData
##   sampleNames: 0 hr rep 3 2 hr rep 3 ... 24 hr rep 3 (6 total)
##   varLabels: Tag Time Replicate
##   varMetadata: labelDescription
## featureData
##   featureNames: 84447 84448 ... 126299 (41853 total)
##   fvarLabels: Checked Confidence ... PSM.count (42 total)
##   fvarMetadata: labelDescription
## experimentData: use 'experimentData(object)'
## Annotation:  
## - - - Processing information - - -
## Subset [218958,6][41853,6] Tue Jan 12 14:42:27 2021 
##  MSnbase version: 2.14.2
dim(psms_lpsTimecourse_rep3_mulvey2021)
## [1] 41853     6

The summary above tells us that 34640, 49806 and 41853 PSMs were detected in replicates 1, 2 and 3, respectively.

In the below code chunk we put the replicates into a MSnSetList for ease of data processing.

psms <- MSnSetList(list(psms_lpsTimecourse_rep1_mulvey2021,
                        psms_lpsTimecourse_rep2_mulvey2021,
                        psms_lpsTimecourse_rep3_mulvey2021))

3.2.1 Missing values assessent

It is important to examine any missing values, whether they are biological or technical. In MS proteomics experiments that use isobaric labelling, such as we have here, we find very few missing values and it is easy to examine them on case-by-case basis. In the below code chunks we indeed see that there are very few PSMs with missing values after filtering. There are 19 missing values in rep 1, 7 in rep2, and 16 in rep3.

As we have such few missing values we begin by assessing if we have any other PSMs corresponding to the same protein group available for quantitation. The below code chunk counts the number of PSMs available for a protein group, for those protein groups which have missing values.

## Display number of PSMs for each protein with a MV
xx <- lapply(psms, function(z) which(apply(exprs(z), 1, anyNA)))
(numpsms <- sapply(seq(psms), function(y)
  {sapply(fData(psms[[y]])$Master.Protein.Accessions[xx[[y]]],
       function(z) length(which(fData(psms[[y]])$Master.Protein.Accessions 
                                == z)))}))
## [[1]]
##   P04179   P04179   P09914   O14879   P06307   Q8TCB0   P09601   P09601 
##        6        6       15       25        1        2       15       15 
##   O14879   P09913   P00973   P01584   P04075   Q9BYX4   O14879   P20591 
##       25       16        7        7       84       12       25       41 
## P29728-2   Q9BYX4   Q13501 
##       15       12       12 
## 
## [[2]]
##   O14879   P04179   P12956   P09913   O14879   P09601 P11388-4 
##       33       11       55       19       33       24       43 
## 
## [[3]]
##   P20591   P09914   Q13501   P20591   P20591   Q9BYX4   P20591   O14879 
##       68       21       14       68       68        9       68       30 
##   Q15833 Q96J88-2   P05362   P09913   Q9BYK8 Q96J88-2   O14879   P20592 
##       16        8       19       13       17        8       30       38
sapply(xx, length)
##  1  2  3 
## 19  7 16

We find that there is only one singleton PSM (one protein group with one only PSM for quantitation) in rep 1 (P06307). The below code chunk plots the PSM quantitation profiles of every protein group that has a missing PSM. Red triangles highlight the missing value.

## plot reps
plotMV <- function(r) {
  tochk <- unique(names(numpsms[[r]]))
  for (i in 1:length(tochk)) {
  mm <- exprs(psms[[r]])[which(fData(psms[[r]])$Master.Protein.Accessions == tochk[i]), , drop = FALSE]
  matplot(t(mm), type = "l", xlab = "", ylab = "m/z", lwd = 2, 
          main = tochk[i], col = "grey", axes = FALSE)
  axis(2)
  axis(1, at = 1:ncol(mm), labels = colnames(mm), las = 2)
  if (any(is.na(mm))) {
    ycoord <- sapply(1:ncol(mm), function(z) which(is.na(mm[, z])))
  .l <- sapply(ycoord, length)
  .xi <- which(.l > 0)
  xcoord <- unlist(lapply(1:length(.xi), function(z) rep(.xi[z], .l[.xi[z]])))
  ycoord <- unlist(ycoord)
  points(x = xcoord, y = ycoord, bg = "red", col = "black", pch = 24, cex = 1.5)}
  }
}

par(mfrow = c(4, 4))
plotMV(r = 1)

par(mfrow = c(2, 4))
plotMV(r = 2)

par(mfrow = c(3, 4))
plotMV(r = 3)

Figure X. PSM quantitation profile plots showing for each protein group that has a missing value in each replciate. The red triangles show the location of the missing values in the timecourse.

Looking at the above plots we see for the PSMs that are missing there are two general trends - they occur at the lower end/start of the time course - missing PSMs tend to appear at the beginning of the time-course and show the same trend over time (a few exceptions listed below)

Given these two observations we decide for the majority of PSMs to use a left-censored imputation approach. It wouldn’t be appropriate for a few cases and these are P06307, P04075 in rep 1, P113388-4, P12956 in rep 2 and so for these PSMs we will use k-nearest neighbour (k-NN), and thus we use a mixed imputation strategy. We remove the PSM at row 31435 for the protein group P12956 as it has 5 missing values out of 6, and there are many other PSMs available for protein quantitation.

Type ?impute in your R console for more information on missing values imputation methods available in the MSnbase and pRoloc pipeline.

Imputation is done using the impute function in MSnbase

## Split the data by replicate
psms_rep1 <- psms[[1]]
psms_rep2 <- psms[[2]]
psms_rep3 <- psms[[3]]

## define psms which are missing at random - see ?impute
fData(psms_rep1)$randna <- FALSE
fData(psms_rep1)$randna[11237] <- TRUE      # P06307
fData(psms_rep1)$randna[26194] <- TRUE      # P04075

# remove missing this PSM for protein P12956 as it has 5 missing values out of the 6 timepoints
# remove PSM 48162 for protein P113388-4 as 42 other PSMs available and it has no clear trend
psms_rep2 <- psms_rep2[-c(31435, 48162), ]

## replicate 1 imputation
psms_rep1 <- impute(psms_rep1, "mixed", 
                    randna = fData(psms_rep1)$randna, 
                    mar = "knn",
                    mnar = "MinDet")

## replicate 2 imputation                
psms_rep2 <- impute(psms_rep2, "MinDet")

## replicate 3 imputation
psms_rep3 <- impute(psms_rep3, "MinDet")

## Re-create MSnSetList of the data
psms <- MSnSetList(list(rep1 = psms_rep1, rep2 = psms_rep2, rep3 = psms_rep3))

We can check these look sensible by re-plotting them again.

## Display number of PSMs for each protein with a MV
numpsms <- sapply(seq(psms), function(y)
  {sapply(fData(psms[[y]])$Master.Protein.Accessions[xx[[y]]],
       function(z) length(which(fData(psms[[y]])$Master.Protein.Accessions 
                                == z)))})

par(mfrow = c(4, 4))
plotMV(r = 1)

par(mfrow = c(2, 4))
plotMV(r = 2)

par(mfrow = c(3, 4))
plotMV(r = 3)

3.2.2 Data normalisation

The data was visually assessed by examining pairwise scatter plots and MAplots of the data and then normalised using vsn followed by diff.median.

## make all PSMs uppercase so that PSMs with different PTMs are not
## considered as different sequences
for (i in seq(psms)) 
  fData(psms@x[[i]])$Annotated.Sequence <- toupper(fData(psms@x[[i]])$Annotated.Sequence)


## combine the data and normalise
cmb <- MSnbase::combine(psms[[1]], psms[[2]])
cmb <- MSnbase::combine(cmb, psms[[3]])
dim(cmb)

## Let's have a look at the intensities
boxplot(log(exprs(cmb)))
## Try a few different normalisation approaches
cmb.vsn <- normalise(cmb, "vsn")
cmb.median <- normalise(cmb, "diff.median")
cmb.vsn.median <- normalise(cmb.vsn, "diff.median")
cmb.quantiles <- normalise(cmb, "quantiles")

## Plot the data
par(mfrow = c(2, 3), las = 2, oma = c(6, 1, 1, 1))
boxplot(log(exprs(cmb)), main = "method = untransformed", ylab = "log intensity")
boxplot(log(exprs(cmb.median)), main = "method = diff.median", ylab = "log intensity")
boxplot(exprs(cmb.vsn), main = "method = vsn", ylab = "log intensity")    # NOTE: VSN internally logs the data 
boxplot(log(exprs(cmb.vsn.median)), main = "method = vsn + diff.median", ylab = "log intensity")
boxplot(log(exprs(cmb.quantiles)), main = "method = quantiles", ylab = "log intensity")

## We decide to go with 'vsn' followed by 'diff.median'
cmb <- cmb.vsn.median

Figure X. Boxplots displaying the (log) intensities of PSMs after applying different normalisation methods available in MSnbase

3.2.3 Aggregation to protein

We use the combineFeatures function from MSnbase to aggregate to protein level.

psms1 <- filterNA(cmb[, 1:6])
psms2 <- filterNA(cmb[, 7:12])
psms3 <- filterNA(cmb[, 13:18])
psms <- MSnSetList(list(psms1, psms2, psms3))

## Aggregate to protein using median PSM per protein group
prots <- lapply(seq(psms), function(z)
               combineFeatures(psms[[z]], 
                               groupBy = fData(psms[[z]])$Master.Protein.Accession,
                               method = "median", verbose = FALSE))


## Do data fusion - create concatenated complete dataset
prots[[1]] <- updateFvarLabels(prots[[1]])
prots[[2]] <- updateFvarLabels(prots[[2]])
prots[[3]] <- updateFvarLabels(prots[[3]])
cmbprots <- MSnbase::combine(prots[[1]], prots[[2]])
cmbprots <- MSnbase::combine(cmbprots, prots[[3]])
cmbprots <- MSnbase::filterNA(cmbprots)
dim(cmbprots)
## [1] 4292   18

We identify 5221, 5812 and 5210 proteins in replicates 1, 2 and 3, respectively, and a total of 4292 common across all channels and timepoints in all samples.

3.3 Protein level data processing

We load the individual protein level datasets from pRolocdata using the data function.

data("lpsTimecourse_rep1_mulvey2021")
data("lpsTimecourse_rep2_mulvey2021")
data("lpsTimecourse_rep3_mulvey2021")

lpsTimecourse_rep1_mulvey2021
## MSnSet (storageMode: lockedEnvironment)
## assayData: 5221 features, 6 samples 
##   element names: exprs 
## protocolData: none
## phenoData
##   sampleNames: X0.hr.rep.1 X2.hr.rep.1 ... X24.hr.rep.1 (6 total)
##   varLabels: Tag Time Replicate
##   varMetadata: labelDescription
## featureData
##   featureNames: A0AVT1 A0FGR8-2 ... Q9Y6Y8 (5221 total)
##   fvarLabels: Checked.rep1 Confidence.rep1 ... Peptides.count.rep1 (44
##     total)
##   fvarMetadata: labelDescription
## experimentData: use 'experimentData(object)'
## Annotation:  
## - - - Processing information - - -
##  MSnbase version: 2.14.2
dim(lpsTimecourse_rep1_mulvey2021)
## [1] 5221    6
lpsTimecourse_rep2_mulvey2021
## MSnSet (storageMode: lockedEnvironment)
## assayData: 5812 features, 6 samples 
##   element names: exprs 
## protocolData: none
## phenoData
##   sampleNames: X0.hr.rep.2 X2.hr.rep.2 ... X24.hr.rep.2 (6 total)
##   varLabels: Tag Time Replicate
##   varMetadata: labelDescription
## featureData
##   featureNames: A0A0B4J2F0 A0AV96 ... Q9Y6Y8 (5812 total)
##   fvarLabels: Checked.rep2 Confidence.rep2 ... Peptides.count.rep2 (43
##     total)
##   fvarMetadata: labelDescription
## experimentData: use 'experimentData(object)'
## Annotation:  
## - - - Processing information - - -
##  MSnbase version: 2.14.2
dim(lpsTimecourse_rep2_mulvey2021)
## [1] 5812    6
lpsTimecourse_rep3_mulvey2021
## MSnSet (storageMode: lockedEnvironment)
## assayData: 5210 features, 6 samples 
##   element names: exprs 
## protocolData: none
## phenoData
##   sampleNames: X0.hr.rep.3 X2.hr.rep.3 ... X24.hr.rep.3 (6 total)
##   varLabels: Tag Time Replicate
##   varMetadata: labelDescription
## featureData
##   featureNames: A0AV96 A0AVT1 ... Q9Y6Y8 (5210 total)
##   fvarLabels: Checked.rep3 Confidence.rep3 ... Peptides.count.rep3 (43
##     total)
##   fvarMetadata: labelDescription
## experimentData: use 'experimentData(object)'
## Annotation:  
## - - - Processing information - - -
##  MSnbase version: 2.14.2
dim(lpsTimecourse_rep3_mulvey2021)
## [1] 5210    6

We see we have 5221, 5812 and 5210 proteins for replicates 1, 2 and 3, respectively.

3.3.1 Data fusion

Data is concatenated into one matrix using the combine function in MSnbase and then proteins not common across all replicates are removed by using the filterNA function.

cmb <- MSnbase::combine(lpsTimecourse_rep1_mulvey2021, 
                        lpsTimecourse_rep2_mulvey2021)
cmb <- MSnbase::combine(cmb, lpsTimecourse_rep3_mulvey2021)
cmb <- filterNA(cmb)
dim(cmb)
## [1] 4292   18

We find 4292 proteins common across all timepoints in all replicates.

source("r/ggVolcano.R")
## Add Gene Names to the data for adding Gene Name labels when we plot the results
cmb <- addGeneName(cmb, fcol = "Protein.Descriptions.rep1", col.name = "GN")

Note: the combined dataset is also available directly from pRolocdata type data("lpsTimecourse_mulvey2021")

3.4 Protein expression analysis

3.4.1 Batch effects

Before we can have a look at changes in protein expression we first need to check if we see any batch effects. We plot the data by ‘Tag’ and ‘Replicate’ to see if this may be the case.

par(mfrow = c(1, 2))
plot2D(t(cmb), fcol = "Tag", main = "PCA: TMT tag")
plot2D(t(cmb), fcol = "Replicate", main = "PCA: Replicates")

We see clearly from the PCA that the samples are confounded by replicate. We can correct for this by using limma. The limma package in Bioconductor provides a number of linear models that can accommodate complex experimental designs and account for experimental technicalities such as batch effects and confounding factors. To statistically analyse relative abundance we use limma’s empirical Bayes moderated t-test, a shrinkage method appropriate for small sample sizes (see Smyth’s paper: http://www.ncbi.nlm.nih.gov/pubmed/16646809). A paired t-test is valid as experiments within each replicate use the same lysate.

3.4.2 Differential protein expression analysis

We use the classic eBayes method in the limma package using lmFit and add Replicate to the model matrix so we can account for this confounding factor i.e. design <- model.matrix(~Replicate+Time). limma takes care of these potential batch effects.

In the next sections we perform differential expression analysis using the limma package [xxx]. We first load some code for generating volcano plots.

source("r/ggVolcano.R")

3.4.2.1 24 hour timepoint

library("limma")
## 0hr and 24hr
i <- grep("X0.hr", sampleNames(cmb))
j <- grep("X24.hr", sampleNames(cmb))
eset <- cmb[, c(i, j)]
data <- exprs(eset)
rownames(data) <- fData(cmb)[["GN"]]
head(data)
##           X0.hr.rep.1 X0.hr.rep.2 X0.hr.rep.3 X24.hr.rep.1 X24.hr.rep.2
## UBA6         4.579279    5.242608    4.844744     4.871262     5.282477
## ESYT2        4.875324    5.136520    4.496599     4.397845     4.994216
## UHRF1BP1L    4.488927    3.614179    4.463849     4.480803     3.777839
## SHTN1        4.926983    4.752739    4.299910     5.145240     4.807961
## ILVBL        4.936206    4.395639    3.690728     4.480962     4.126668
## SH3PXD2B     5.335262    3.672522    2.516002     7.062948     5.140535
##           X24.hr.rep.3
## UBA6          5.058791
## ESYT2         4.119505
## UHRF1BP1L     3.843625
## SHTN1         4.651524
## ILVBL         3.573552
## SH3PXD2B      3.916628
## Create a model matrix
Time <- as.factor(pData(eset)$Time)
Replicate <- as.factor(pData(eset)$Replicate)
(design <- model.matrix(~Replicate+Time))
##   (Intercept) Replicate2 Replicate3 Time24
## 1           1          0          0      0
## 2           1          1          0      0
## 3           1          0          1      0
## 4           1          0          0      1
## 5           1          1          0      1
## 6           1          0          1      1
## attr(,"assign")
## [1] 0 1 1 2
## attr(,"contrasts")
## attr(,"contrasts")$Replicate
## [1] "contr.treatment"
## 
## attr(,"contrasts")$Time
## [1] "contr.treatment"
## Perform the fit
fit <- lmFit(data, design)
fit <- eBayes(fit)

## Get the results
res_t24 <- topTable(fit, coef = "Time24", 
                 adjust.method="BH", number = Inf)
res_t24 <- addPlottingInfo(res_t24)
msnset_t24 <- addLimmaInfo(cmb, res_t24)

## Look at p-value distribution
hist(res_t24[, "P.Value"], breaks = 50, 
     main = "Histogram of p-values for 0/24hr LPS \n(limma's paired moderated t-test)", 
     xlab = "p-value")
## save data as a TSV/CSV
write.exprs(msnset_t24, fDataCols = 131:138,  sep = "\t", 
            file = "csv/timecourse_t24.tsv")

It’s always a good idea to check the distribution of your raw uncorrected p-values. The easiest way to do this is to make a histogram of your p-values as per the above code chunk. This is a great post on how to interpret your p-values. We find we have a nice anti-conservative distribution.

The object res_24 is a data.frame containing the output from running topTable which is a function to extract a table of the top-ranked genes/proteins from the limma linear model fit.

We can now take this output and append it to our MSnSet and then plot the results. The classic way to present gene/protein expression data is a volcano plot.

The ggVolcano function plots the results on a volcano plot.

library("ggplot2")
library("dplyr")
library("ggrepel")
ggVolcano(res_t24, "24 hours", N = 20, lfc = 0.6, p = 0.01, 
          addLegend = TRUE, text.size = 12, base.text.size = 12, 
          legend.text.size = 12, geom.point.size = 2, 
          label.text.size = 4)

#### 12 hour timepoint

We repeat the analysis at each time point and look to see if we find any proteins that are significantly up- or downregulated.

## 0hr and 12hr
i <- grep("X0.hr", sampleNames(cmb))
j <- grep("X12.hr", sampleNames(cmb))
eset <- cmb[, c(i, j)]
data <- exprs(eset)
rownames(data) <- fData(cmb)[["GN"]]
head(data)
##           X0.hr.rep.1 X0.hr.rep.2 X0.hr.rep.3 X12.hr.rep.1 X12.hr.rep.2
## UBA6         4.579279    5.242608    4.844744     4.584263     5.190164
## ESYT2        4.875324    5.136520    4.496599     4.594140     5.180463
## UHRF1BP1L    4.488927    3.614179    4.463849     4.505748     3.814994
## SHTN1        4.926983    4.752739    4.299910     4.749325     4.655479
## ILVBL        4.936206    4.395639    3.690728     4.747260     4.037497
## SH3PXD2B     5.335262    3.672522    2.516002     6.088538     3.257607
##           X12.hr.rep.3
## UBA6          4.634475
## ESYT2         4.501894
## UHRF1BP1L     4.022813
## SHTN1         4.252117
## ILVBL         3.502920
## SH3PXD2B      2.956236
## Create a model matrix
Time <- as.factor(pData(eset)$Time)
Replicate <- as.factor(pData(eset)$Replicate)
design <- model.matrix(~Replicate+Time)

## Perform the fit
fit <- lmFit(data, design)
fit <- eBayes(fit)

## Get the results
res_t12 <- topTable(fit, coef = "Time12", 
                 adjust.method="BH", number = Inf)
res_t12 <- addPlottingInfo(res_t12)
msnset_t12 <- addLimmaInfo(cmb, res_t12)

## Look at p-value distribution
hist(res_t12[, "P.Value"], breaks = 50, 
     main = "Histogram of p-values for 0/12hr LPS \n(limma's paired moderated t-test)", 
     xlab = "p-value")
## save data as a TSV/CSV
write.exprs(msnset_t12, fDataCols = 131:138,  sep = "\t", 
            file = "csv/timecourse_t12.tsv")

## plot volcanos
ggVolcano(res_t12, "12 hours", N = 20, lfc = 0.6, 
          p = 0.01, addLegend = FALSE,
          text.size = 12, base.text.size = 12, 
          legend.text.size = 12, geom.point.size = 2, 
          label.text.size = 4)
## Warning: Removed 12 rows containing missing values (geom_label_repel).

3.4.2.2 6 hour timepoint

## 0hr and 6r
i <- grep("X0.hr", sampleNames(cmb))
j <- grep("X6.hr", sampleNames(cmb))
eset <- cmb[, c(i, j)]
data <- exprs(eset)
rownames(data) <- fData(cmb)[["GN"]]
head(data)
##           X0.hr.rep.1 X0.hr.rep.2 X0.hr.rep.3 X6.hr.rep.1 X6.hr.rep.2
## UBA6         4.579279    5.242608    4.844744    4.478218    5.096956
## ESYT2        4.875324    5.136520    4.496599    4.913246    5.250985
## UHRF1BP1L    4.488927    3.614179    4.463849    4.546953    4.030109
## SHTN1        4.926983    4.752739    4.299910    4.968413    4.618690
## ILVBL        4.936206    4.395639    3.690728    4.783785    4.140193
## SH3PXD2B     5.335262    3.672522    2.516002    5.255045    3.250098
##           X6.hr.rep.3
## UBA6         4.839907
## ESYT2        4.330492
## UHRF1BP1L    4.313316
## SHTN1        4.708527
## ILVBL        4.019145
## SH3PXD2B     2.282174
## Create a model matrix
Time <- as.factor(pData(eset)$Time)
Replicate <- as.factor(pData(eset)$Replicate)
design <- model.matrix(~Replicate+Time)

## Perform the fit
fit <- lmFit(data, design)
fit <- eBayes(fit)

## Get the results
res_t6 <- topTable(fit, coef = "Time6", 
                 adjust.method="BH", number = Inf)
res_t6 <- addPlottingInfo(res_t6)
msnset_t6 <- addLimmaInfo(cmb, res_t6)

## Look at p-value distribution
hist(res_t6[, "P.Value"], breaks = 50, 
     main = "Histogram of p-values for 0/6hr LPS \n(limma's paired moderated t-test)", 
     xlab = "p-value")
## save data as a TSV/CSV
write.exprs(msnset_t6, fDataCols = 131:138,  sep = "\t", 
            file = "csv/timecourse_t6.tsv")

## plot volcanos
ggVolcano(res_t6, "6 hours", N = 20, lfc = 0.6, 
          p = 0.01, addLegend = FALSE,
          text.size = 12, base.text.size = 12, 
          legend.text.size = 12, geom.point.size = 2, 
          label.text.size = 4)

3.4.2.3 4 hour timepoint

## 0hr and 4hr
i <- grep("X0.hr", sampleNames(cmb))
j <- grep("X4.hr", sampleNames(cmb))
eset <- cmb[, c(i, j)]
data <- exprs(eset)
rownames(data) <- fData(cmb)[["GN"]]
head(data)
##           X0.hr.rep.1 X0.hr.rep.2 X0.hr.rep.3 X4.hr.rep.1 X4.hr.rep.2
## UBA6         4.579279    5.242608    4.844744    4.674778    5.360775
## ESYT2        4.875324    5.136520    4.496599    4.993125    5.268153
## UHRF1BP1L    4.488927    3.614179    4.463849    4.604388    3.659608
## SHTN1        4.926983    4.752739    4.299910    5.127168    4.831314
## ILVBL        4.936206    4.395639    3.690728    4.810118    4.155659
## SH3PXD2B     5.335262    3.672522    2.516002    5.334079    3.722221
##           X4.hr.rep.3
## UBA6         4.844971
## ESYT2        4.744458
## UHRF1BP1L    4.296213
## SHTN1        4.886162
## ILVBL        3.649692
## SH3PXD2B     2.313917
## Create a model matrix
Time <- as.factor(pData(eset)$Time)
Replicate <- as.factor(pData(eset)$Replicate)
design <- model.matrix(~Replicate+Time)

## Perform the fit
fit <- lmFit(data, design)
fit <- eBayes(fit)

## Get the results
res_t4 <- topTable(fit, coef = "Time4", 
                 adjust.method="BH", number = Inf)
res_t4 <- addPlottingInfo(res_t4)
msnset_t4 <- addLimmaInfo(cmb, res_t4)

## Look at p-value distribution
hist(res_t4[, "P.Value"], breaks = 50, 
     main = "Histogram of p-values for 0/4hr LPS \n(limma's paired moderated t-test)", 
     xlab = "p-value")
## save data as a TSV/CSV
write.exprs(msnset_t4, fDataCols = c(131:138),  sep = "\t", 
            file = "csv/timecourse_t4.tsv")

## plot volcanos
ggVolcano(res_t4, "4 hours", N = 20, lfc = 0.6, 
          p = 0.01, addLegend = FALSE,
          text.size = 12, base.text.size = 12, 
          legend.text.size = 12, geom.point.size = 2, 
          label.text.size = 4)

3.4.2.4 2 hour timepoint

## 0hr and 2hr
i <- grep("X0.hr", sampleNames(cmb))
j <- grep("X2.hr", sampleNames(cmb))
eset <- cmb[, c(i, j)]
data <- exprs(eset)
rownames(data) <- fData(cmb)[["GN"]]
head(data)
##           X0.hr.rep.1 X0.hr.rep.2 X0.hr.rep.3 X2.hr.rep.1 X2.hr.rep.2
## UBA6         4.579279    5.242608    4.844744    4.359477    5.190733
## ESYT2        4.875324    5.136520    4.496599    4.674483    5.146304
## UHRF1BP1L    4.488927    3.614179    4.463849    4.478383    3.873257
## SHTN1        4.926983    4.752739    4.299910    4.891723    4.652700
## ILVBL        4.936206    4.395639    3.690728    4.543970    4.174060
## SH3PXD2B     5.335262    3.672522    2.516002    5.101335    4.315416
##           X2.hr.rep.3
## UBA6         4.691351
## ESYT2        4.753582
## UHRF1BP1L    4.712662
## SHTN1        4.917707
## ILVBL        3.817152
## SH3PXD2B     2.197979
## Create a model matrix
Time <- as.factor(pData(eset)$Time)
Replicate <- as.factor(pData(eset)$Replicate)
design <- model.matrix(~Replicate+Time)

## Perform the fit
fit <- lmFit(data, design)
fit <- eBayes(fit)

## Get the results
res_t2 <- topTable(fit, coef = "Time2", 
                 adjust.method="BH", number = Inf)
res_t2 <- addPlottingInfo(res_t2)
msnset_t2 <- addLimmaInfo(cmb, res_t2)

## Look at p-value distribution
hist(res_t2[, "P.Value"], breaks = 50, 
     main = "Histogram of p-values for 0/2hr LPS \n(limma's paired moderated t-test)", 
     xlab = "p-value")
## save data as a TSV/CSV
write.exprs(msnset_t2, fDataCols = 131:138,  sep = "\t", 
            file = "csv/timecourse_t2.tsv")

## plot volcanos
ggVolcano(res_t2, "2 hours", N = 20, lfc = 0.6, 
          p = 0.05, addLegend = FALSE,
          text.size = 12, base.text.size = 12, 
          legend.text.size = 12, geom.point.size = 2, 
          label.text.size = 4)

3.4.3 Summary

Interleukin 1-beta (IL1B) was the only protein found to be significantly upregulated at all tine points. Upregulated cellular levels of IL1B correlated with increased IL1B secretion into the cell supernatant (see figure XXX below).

source("r/plotLineGraph.R")
library("tidyr")
library("Rmisc")
library("gridExtra")

## read in elisa data
elisa_il1b_wide <- read.csv("csv/elisa_il1b.csv")
elise_cxcl_wide <- read.csv("csv/elisa_cscl10.csv")

## convert to long tidy format for ggplot
elisa_il1b_long <- gather(elisa_il1b_wide, replicate, measurement, X1:X6, factor_key = TRUE)
elisa_cxcl10_long <- gather(elise_cxcl_wide, replicate, measurement, X1:X6, factor_key = TRUE)

p <- 
  plotLineGraph(elisa_il1b_wide, "median", linecol = "#1B83E9") +
  ggtitle("Elisa data: IL-1B") + 
  theme(plot.title = element_text(hjust = 0.5))

q <- plotLineGraph(elise_cxcl_wide, "median", linecol = "#1B83E9") +
  ggtitle("Elisa data: CXCL10") + 
  theme(plot.title = element_text(hjust = 0.5))

## get quantitation data
IL1B <- "P01584"
CXCL10 <- "P02778"

## Load protein data
x1 <- lpsTimecourse_rep1_mulvey2021
x2 <- lpsTimecourse_rep2_mulvey2021
x3 <- lpsTimecourse_rep3_mulvey2021

## convert to long tidy format for ggplot
lopit_il1b_wide <- t(rbind(exprs(x1[IL1B,]), exprs(x2[IL1B,]),exprs(x3[IL1B,])))
lopit_cxcl10_wide <- t(rbind(exprs(x1[CXCL10,]), exprs(x2[CXCL10,]),exprs(x3[CXCL10,])))
lopit_il1b_wide <- data.frame(cbind(Time = c(0, 2, 4, 6, 12, 24), lopit_il1b_wide))
lopit_cxcl10_wide <- data.frame(cbind(Time = c(0, 2, 4, 6, 12, 24), lopit_cxcl10_wide))
colnames(lopit_cxcl10_wide) <- colnames(lopit_il1b_wide) <- c("Time", "X1", "X2", "X6")
lopit_il1b_long <- gather(lopit_il1b_wide, replicate, measurement, X1:X6, factor_key = TRUE)
lopit_cxcl10_long <- gather(lopit_cxcl10_wide, replicate, measurement, X1:X6, factor_key = TRUE)

## plot quantitation profiles
r = plotLineGraph(lopit_il1b_wide, 
                  linecol = "#C50000", 
                  .ylab = "Abundance\n",
                  themeSize = 14) +
  ylim(c(1.5, 7)) +
  ggtitle("Shotgun: IL-1B") + 
  theme(plot.title = element_text(hjust = 0.5))
s = plotLineGraph(lopit_cxcl10_wide, 
                  linecol = "#C50000", 
                  .ylab = "Abundance\n",
                  themeSize = 14) +
  ylim(c(1.5, 7)) +
  ggtitle("Shotgun: CXCL10") + 
  theme(plot.title = element_text(hjust = 0.5))

grid.arrange(p, q, r, s, ncol = 2, nrow = 2)

12 hour heatmap

library("gplots")
library("plotrix")
library("scico")

## restructure the gather the data for heatmap
dat <- cbind(exprs(msnset_t12), fData(msnset_t12))
rownames(dat) <- NULL
dat <- dat %>% 
  filter(Significance != "Not significant") %>% 
  tibble::column_to_rownames("GN") %>% 
  select(1:18) %>% as.matrix()
calcMeds <- function(df, .grepName) {
  ind <- grep(.grepName, colnames(df))
  .df <- df[, ind]
  .mean <- rowMedians(.df)
  return(.mean)
}
t0 <- calcMeds(dat, "X0.hr")
t2 <- calcMeds(dat, "X2.hr")
t4 <- calcMeds(dat, "X4.hr")
t6 <- calcMeds(dat, "X6.hr")
t12 <- calcMeds(dat, "X12.hr")
t24 <- calcMeds(dat, "X24.hr")
dat_meds <- cbind(t0, t2, t4, t6, t12, t24)
rownames(dat_meds) <- rownames(dat)
colnames(dat_meds) <- c(0, 2, 4, 6, 12, 24)
head(dat_meds)
##              0        2        4        6       12       24
## IFIT3 2.730084 3.055495 4.169061 5.354609 6.142207 6.638291
## CETN3 5.825406 5.125166 5.315861 5.242166 4.666942 4.932849
## ZEB2  4.142148 4.718323 4.614038 4.773679 5.162687 5.100232
## ABCA1 3.824979 3.926887 4.264135 5.227943 5.590898 5.126276
## NADK  3.492452 3.460054 3.345293 3.647640 4.756142 5.302163
## DDX58 3.991000 3.916902 3.878920 4.849751 5.499738 5.950881
## set colour palette - use the scico package
my_palette <- scico(72, palette = "berlin")
col_breaks = c(seq(-1,1,length=35), 
               seq(0,0.8,length=11),  
               seq(1,0,length=35)) 

## Plot heatmap
heatmap.2(dat_meds, density.info = "none", trace = "none", 
          col = my_palette, Colv = NA, dendrogram = "row", 
          key=TRUE, cexCol = 2, cexRow = 1.3, margin = c(6,8), 
          lhei=c(.8,3.5), lwid=c(0.2,0.8), offsetRow=0.3)

3.5 Bayesian temporal clustering

Bayesian correlated clustering was conducted on the shotgun proteomic time-course series to capture clusters of co-regulated proteins and temporal patterns of protein expression in response to LPS. Replicate experiments were combined using multiple dataset integration MDI5 where the number of clusters were automatically inferred.

Bayesian inference was performed using Markov-chain Monte Carlo (MCMC) as implemented in the MDI-GPU software.5 Clusters were extracted only for proteins that were consistently allocated to the same clusterings (sampled from the posterior distribution) across replicates (the results can be found in Supplementary Table 3 of Mulvey et al. and also in the csv directory of this Git repo).

In the below code chunk we load the results from temporal clustering and plot 12 clusters of interest as discussed in Mulvey et al. (Figure 2 of2)

library("RColorBrewer")
source("r/plotRibbons.R")

## Read in MDI clustering results
mdi_rep1 <- read.csv("csv/mdi_clustering_lpsrep1.csv")
mdi_rep2 <- read.csv("csv/mdi_clustering_lpsrep2.csv")
mdi_rep3 <- read.csv("csv/mdi_clustering_lpsrep3.csv")
mdi <- read.csv("csv/mdi_clusters_output.csv")

# get median normalised intensity
dat_mat <- list(data.matrix(mdi_rep1[, -1]), 
                data.matrix(mdi_rep2[, -1]), 
                data.matrix(mdi_rep3[, -1]))
av_mat <- apply(simplify2array(dat_mat), c(1,2), median)
rownames(av_mat) <- mdi_rep1[,1]
colnames(av_mat) <- c(0, 2, 4, 6, 12, 24)
head(av_mat)
##                 0          2          4           6        12       24
## P02795 -1.0406225 -0.8355461 -0.6604120  0.13893606 1.0759237 1.258747
## P04179 -0.6899927 -0.6821170 -0.5093226 -0.30338106 0.1302568 1.952588
## O14879 -0.8874299 -0.8758429 -0.5680529 -0.01642289 0.6585623 1.661210
## Q9BYX4 -0.8292398 -0.8356126 -0.6969017 -0.08215123 0.9237005 1.515785
## P00973 -0.8814504 -0.8015016 -0.6501797 -0.11263572 0.8438697 1.578594
## P05362 -0.8188745 -0.7149888 -0.4673084 -0.22529338 0.5581240 1.756882
## plot clusters of interest
ids <- c(1:6, 9:11, 13, 14, 17)
mdi_profs <- sapply(ids, function(z) 
  av_mat[mdi$Protein.Accession[which(mdi$Cluster.Number == z)], ])
names(mdi_profs) <- paste0("cluster", ids)

## pick a good palette
ribbonCol <- c(brewer.pal(n = 9, "Oranges")[-c(1:3)],
           brewer.pal(n = 9, "GnBu")[-c(1:3)])

par(mfrow = c(3,2), mar = c(6, 5, 3, 3))
for (i in 1:6) {
  ribbonPlot(mdi_profs[[i]], col = ribbonCol[i], c(0.05, .95), 
             main = paste("Cluster", ids[i]), cex.main = 2, size = 2)
}

par(mfrow = c(3,2), mar = c(6, 5, 3, 3))
for (i in 7:12) {
  ribbonPlot(mdi_profs[[i]], col = ribbonCol[i], c(0.05, .95), 
             main = paste("Cluster", ids[i]), cex.main = 2, size = 2)
}

3.6 Gene Ontology enrichment

Temporal clusters were functionally annotated by using the Gene Ontology (GO) [xxx]. For each cluster output from the Bayesian temporal clustering, in turn, an enrichment test for biological process (BP), cellular component (CC) and molecular function (MF) annotations, was carried out using the Database for Annotation, Visualisation and Integrated Discovery (DAVID v6.8; https://david.ncifcrf.gov/) where a cutoff of < 0.05 was applied according to the Benjamini-Hochberg procedure [xxx]. The GO annotation enrichment results are found in Supplementary Table 4 of Mulvey et al 2021.

In the below code chunk some of the results from the GO enrichment (Figure 3 of Mulvey et al. 2021). GOCC, GOMF and GOBP annotation term enrichment for the the clusters are shown below where the -log10(adjusted p-value) is shown along the x-axis and most highly enriched term along the y-axis. The numbers within each barplot refer to the number of proteins associated with the term in each cluster.

source("r/GObarplot.R")
GObarplots(filename = "csv/david_data_gobp.csv", cols = ribbonCol, N = 30) +
  ggtitle("GO Biological Process Terms")

GObarplots(filename = "csv/david_data_gocc.csv", cols = ribbonCol) +
  ggtitle("GO Cellular Component Terms")

GObarplots(filename = "csv/david_data_gomf.csv", cols = ribbonCol, N = 15) +
  ggtitle("GO Molecular Function Terms")

The timecourse of LPS provides an insight into the protein expression of THP-1 proteome during the initial phases of a pro-inflammatory response. In order the gain a complete picture of protein relocalisation events we performed a series of hyperLOPIT experiments and 0 and 12 hour LPS time-points to give a deeper spaial understanding of the dynamic changes that occur in the proteome following stimulation.

4 Spatial proteomics data analysis

4.1 The hyperLOPIT workflow

Using the hyperLOPIT proteomics platform6 we obtained spatial maps that capture the steady-state distribution of thousands of proteins in the THP-1 human leukaemia cell line. Experiments were conducted in triplicate and the samples were analysed and collected when cells were (1) unstimulated and then (2) at 12 hours following stimulation with LPS.

The hyperLOPIT method combines biochemical cell fractionation with multiplexed high-resolution mass-spectrometry,6 the full protocol and experimental design can be found in.2 Briefly, TMT labelling was conducted as described in7 and 20 fractions including the cytosol-enriched fraction were labelled per gradient, by combining two TMT 10-plex experiments in an interleaved labelling design to capture as much subcellular diversity as possible. The experimental design for each dataset is stored in the pData slot of each dataset.

To access the full experimental design use the pData function

## TMT experiment data for the unstimulated hyperLOPIT data
pData(thpLOPIT_unstimulated_mulvey2021)
##                               Tag    Treatment Replicate Set Fraction
## unstim_rep1_set1_126_cyto     126 Unstimulated         1   1     cyto
## unstim_rep2_set1_126_cyto     126 Unstimulated         2   1     cyto
## unstim_rep3_set1_126_cyto     126 Unstimulated         3   1     cyto
## unstim_rep1_set1_127N_F1.4   127N Unstimulated         1   1     F1.4
## unstim_rep2_set1_127N_F1.6   127N Unstimulated         2   1     F1.6
## unstim_rep3_set1_127N_F1.6   127N Unstimulated         3   1     F1.6
## unstim_rep1_set2_126_F5.6     126 Unstimulated         1   2     F5.6
## unstim_rep1_set1_127C_F7     127C Unstimulated         1   1       F7
## unstim_rep2_set2_127N_F7.8   127N Unstimulated         2   2     F7.8
## unstim_rep3_set2_126_F7.8     126 Unstimulated         3   2     F7.8
## unstim_rep1_set2_127N_F8     127N Unstimulated         1   2       F8
## unstim_rep1_set1_128N_F9     128N Unstimulated         1   1       F9
## unstim_rep3_set1_127C_F9     127C Unstimulated         3   1       F9
## unstim_rep2_set1_127C_F9.10  127C Unstimulated         2   1    F9.10
## unstim_rep1_set2_127C_F10    127C Unstimulated         1   2      F10
## unstim_rep3_set2_127N_F10    127N Unstimulated         3   2      F10
## unstim_rep1_set1_128C_F11    128C Unstimulated         1   1      F11
## unstim_rep3_set1_128N_F11    128N Unstimulated         3   1      F11
## unstim_rep2_set2_127C_F11.12 127C Unstimulated         2   2   F11.12
## unstim_rep1_set2_128N_F12    128N Unstimulated         1   2      F12
## unstim_rep2_set1_128N_F12    128N Unstimulated         2   1      F12
## unstim_rep3_set2_127C_F12    127C Unstimulated         3   2      F12
## unstim_rep1_set1_129N_F13    129N Unstimulated         1   1      F13
## unsti_rep2_set2_128N_F13     128N Unstimulated         2   2      F13
## unstim_rep3_set1_128C_F13    128C Unstimulated         3   1      F13
## unstim_rep1_set2_128C_F14    128C Unstimulated         1   2      F14
## unstim_rep2_set1_128C_F14    128C Unstimulated         2   1      F14
## unstim_rep3_set2_128N_F14    128N Unstimulated         3   2      F14
## unstim_rep1_set1_129C_F15    129C Unstimulated         1   1      F15
## unstim_rep2_set2_128C_F15    128C Unstimulated         2   2      F15
## unstim_rep3_set1_129N_F15    129N Unstimulated         3   1      F15
## unstim_rep1_set2_129N_F16    129N Unstimulated         1   2      F16
## unstim_rep2_set1_129N_F16    129N Unstimulated         2   1      F16
## unstim_rep3_set2_128C_F16    128C Unstimulated         3   2      F16
## unstim_rep1_set1_130N_F17    130N Unstimulated         1   1      F17
## unstim_rep2_set2_129N_F17    129N Unstimulated         2   2      F17
## unstim_rep3_set1_129C_F17    129C Unstimulated         3   1      F17
## unstim_rep1_set2_129C_F18    129C Unstimulated         1   2      F18
## unstim_rep2_set1_129C_F18    129C Unstimulated         2   1      F18
## unstim_rep3_set2_129N_F18    129N Unstimulated         3   2      F18
## unstim_rep1_set1_130C_F19    130C Unstimulated         1   1      F19
## unstim_rep2_set2_129C_F19    129C Unstimulated         2   2      F19
## unstim_rep3_set1_130N_F19    130N Unstimulated         3   1      F19
## unstim_rep1_set2_130N_F20    130N Unstimulated         1   2      F20
## unstim_rep2_set1_130N_F20    130N Unstimulated         2   1      F20
## unstim_rep3_set2_129C_F20    129C Unstimulated         3   2      F20
## unstim_rep1_set1_131_F21      131 Unstimulated         1   1      F21
## unstim_rep2_set2_130N_F21    130N Unstimulated         2   2      F21
## unstim_rep3_set1_130C_F21    130C Unstimulated         3   1      F21
## unstim_rep1_set2_130C_F22    130C Unstimulated         1   2      F22
## unstim_rep2_set1_130C_F22    130C Unstimulated         2   1      F22
## unstim_rep3_set2_130N_F22    130N Unstimulated         3   2      F22
## unstim_rep2_set2_130C_F23    130C Unstimulated         2   2      F23
## unstim_rep3_set1_131_F23      131 Unstimulated         3   1      F23
## unstim_rep1_set2_131_F24      131 Unstimulated         1   2      F24
## unstim_rep2_set1_131_F24      131 Unstimulated         2   1      F24
## unstim_rep3_set2_130C_F24    130C Unstimulated         3   2      F24
## unstim_rep2_set2_131_F25      131 Unstimulated         2   2      F25
## unstim_rep3_set2_131_F26      131 Unstimulated         3   2      F26
## TMT labelling scheme for the LPS hyperLOPIT data
pData(thpLOPIT_lps_mulvey2021)
##                           Tag Treatment Replicate Set Fraction
## lps_rep1_set1_126_cyto    126       LPS         1   1     cyto
## lps_rep2_set1_126_cyto    126       LPS         2   1     cyto
## lps_rep3_set1_126_cyto    126       LPS         3   1     cyto
## lps_rep1_set1_127N_F1.4  127N       LPS         1   1     F1.4
## lps_rep2_set1_127N_F1.6  127N       LPS         2   1     F1.6
## lps_rep3_set1_127N_F1.6  127N       LPS         3   1     F1.6
## lps_rep1_set2_126_F5.7    126       LPS         1   2     F5.7
## lps_rep2_set2_126_F7.8    126       LPS         2   2     F7.8
## lps_rep3_set2_126_F7.8    126       LPS         3   2     F7.8
## lps_rep1_set1_127C_F8    127C       LPS         1   1       F8
## lps_rep1_set2_127N_F9    127N       LPS         1   2       F9
## lps_rep3_set1_127C_F9    127C       LPS         3   1       F9
## lps_rep2_set1_127C_F9.10 127C       LPS         2   1    F9.10
## lps_rep1_set1_128N_F10   128N       LPS         1   1      F10
## lps_rep3_set2_127N_F10   127N       LPS         3   2      F10
## lps_rep1_set2_127C_F11   127C       LPS         1   2      F11
## lps_rep2_set2_127N_F11   127N       LPS         2   2      F11
## lps_rep3_set1_128N_F11   128N       LPS         3   1      F11
## lps_rep1_set1_128C_F12   128C       LPS         1   1      F12
## lps_rep2_set1_128N_F12   128N       LPS         2   1      F12
## lps_rep3_set2_127C_F12   127C       LPS         3   2      F12
## lps_rep1_set2_128N_F13   128N       LPS         1   2      F13
## lps_rep2_set2_127C_F13   127C       LPS         2   2      F13
## lps_rep3_set1_128C_F13   128C       LPS         3   1      F13
## lps_rep1_set1_129N_F14   129N       LPS         1   1      F14
## lps_rep2_set1_128C_F14   128C       LPS         2   1      F14
## lps_rep3_set2_128N_F14   128N       LPS         3   2      F14
## lps_rep1_set2_128C_F15   128C       LPS         1   2      F15
## lps_rep2_set2_128N_F15   128N       LPS         2   2      F15
## lps_rep3_set1_129N_F15   129N       LPS         3   1      F15
## lps_rep1_set1_129C_F16   129C       LPS         1   1      F16
## lps_rep2_set1_129N_F16   129N       LPS         2   1      F16
## lps_rep3_set2_128C_F16   128C       LPS         3   2      F16
## lps_rep1_set2_129N_F17   129N       LPS         1   2      F17
## lps_rep2_set2_128C_F17   128C       LPS         2   2      F17
## lps_rep3_set1_129C_F17   129C       LPS         3   1      F17
## lps_rep1_set1_130N_F18   130N       LPS         1   1      F18
## lps_rep2_set1_129C_F18   129C       LPS         2   1      F18
## lps_rep3_set2_129N_F18   129N       LPS         3   2      F18
## lps_rep1_set2_129C_F19   129C       LPS         1   2      F19
## lps_rep2_set2_129N_F19   129N       LPS         2   2      F19
## lps_rep3_set1_130N_F19   130N       LPS         3   1      F19
## lps_rep1_set1_130C_F20   130C       LPS         1   1      F20
## lps_rep2_set1_130N_F20   130N       LPS         2   1      F20
## lps_rep3_set2_129C_F20   129C       LPS         3   2      F20
## lps_rep1_set2_130N_F21   130N       LPS         1   2      F21
## lps_rep2_set2_129C_F21   129C       LPS         2   2      F21
## lps_rep3_set1_130C_F21   130C       LPS         3   1      F21
## lps_rep1_set1_131_F22     131       LPS         1   1      F22
## lps_rep2_set1_130C_F22   130C       LPS         2   1      F22
## lps_rep3_set2_130N_F22   130N       LPS         3   2      F22
## lps_rep1_set2_130C_F23   130C       LPS         1   2      F23
## lps_rep2_set2_130N_F23   130N       LPS         2   2      F23
## lps_rep3_set1_131_F23     131       LPS         3   1      F23
## lps_rep2_set1_131_F24     131       LPS         2   1      F24
## lps_rep3_set2_130C_F24   130C       LPS         3   2      F24
## lps_rep1_set2_131_F25     131       LPS         1   2      F25
## lps_rep2_set2_130C_F25   130C       LPS         2   2      F25
## lps_rep3_set2_131_F26     131       LPS         3   2      F26

As described in7 the data was processed with Proteome Discoverer v2.1 (Thermo Fisher Scientific) and Mascot server v2.3.02 (Matrix Science) using the SwissProt sequence database for Homo sapiens (www.uniprot.org in November 2016) and the cRAP database (common Repository for Adventitious Proteins, https://www.thegpm.org/crap). Standard filtering of the raw data was conducted as per7 to remove contaminants and low abundant PSMs. A maximum of two missing values per TMT experiment was allowed and the data was directly exported from Proteome Discoverer at the PSM level for downstream analysis in R.

The experiments were done in triplicate for each condition and each replicate contains 2 x TMT 10-plex sets (which we denote as “set 1” and “set 2”). Thus, in total we conducted a total of 12 experiments, 6 per condition wherein we had 2 sets per replicate containing a total of 20 fractions/channels per replicate. One TMT channel was removed (TMT tag 126 in replicate 2, set 2 in the unstimulated) due to erroneous labelling of insoluble material during the sample preparation for this specific tag. The “Average Reporter S/N” value was recalculated for the nine remaining channels in the corresponding 10plex set and PSMs with a value less than 9.0 were discarded (please see Data Processing in the Supplementary Information of2).

4.2 PSM level data processing

4.2.1 Missing values assessment

The 12 PSM level datasets were imported into R and missing values were carefully assessed.

Load the PSM level unstimulated data,

## Unstimulated data
data("psms_thpLOPIT_unstim_rep1_set1")
data("psms_thpLOPIT_unstim_rep1_set2")

data("psms_thpLOPIT_unstim_rep2_set1")
data("psms_thpLOPIT_unstim_rep2_set2")

data("psms_thpLOPIT_unstim_rep3_set1")
data("psms_thpLOPIT_unstim_rep3_set2")

Load the PSM level 12h-LPS stimulated data,

## 12h post LPS-stimulated
data("psms_thpLOPIT_lps_rep1_set1")
data("psms_thpLOPIT_lps_rep1_set2")

data("psms_thpLOPIT_lps_rep2_set1")
data("psms_thpLOPIT_lps_rep2_set2")

data("psms_thpLOPIT_lps_rep3_set1")
data("psms_thpLOPIT_lps_rep3_set2")

For ease of coding we create a list of MSnSets for each condition

psms <- MSnSetList(
  list(
    psms_thpLOPIT_unstim_rep1_set1,
    psms_thpLOPIT_unstim_rep1_set2,
    psms_thpLOPIT_unstim_rep2_set1,
    psms_thpLOPIT_unstim_rep2_set2,
    psms_thpLOPIT_unstim_rep3_set1,
    psms_thpLOPIT_unstim_rep3_set2,
    psms_thpLOPIT_lps_rep1_set1,
    psms_thpLOPIT_lps_rep1_set2,
    psms_thpLOPIT_lps_rep2_set1,
    psms_thpLOPIT_lps_rep2_set2,
    psms_thpLOPIT_lps_rep3_set1,
    psms_thpLOPIT_lps_rep3_set2
  )
)

## add list names for each dataset
.nams <- paste0("Rep", rep(1:3, each = 2), "_set", rep(1:2, 3))
names(psms) <- c(paste0(.nams, "_unstim"), paste0(.nams, "_lps"))

We can view the number of PSMs per experiment,

sapply(psms, nrow)
## Rep1_set1_unstim Rep1_set2_unstim Rep2_set1_unstim Rep2_set2_unstim 
##            44137            41860            39297            37919 
## Rep3_set1_unstim Rep3_set2_unstim    Rep1_set1_lps    Rep1_set2_lps 
##            48199            44802            40994            63844 
##    Rep2_set1_lps    Rep2_set2_lps    Rep3_set1_lps    Rep3_set2_lps 
##            43799            44055            46805            45600

If we examine the corresponding protein group of each PSM with a missing value, we find that there are other PSMs available for quantitation for the majority of proteins. There are still several hundred cases where we only have 1 PSM for a given protein group, so we would lose several hundred proteins per replicate if we were to just remove these PSMs. We assess the missing values across all experiments using the naPlot function to see if there is a trend in where missing values occur.

Generate naPlots,

for (i in seq(psms)) {
  par(las = 2, oma = c(10, 1, 1, 1))
  naplot(psms[[i]], col = "black", las = 2, reorderColumns = FALSE, 
         main = names(psms)[i], cex.axis = 2)
}

Figure 2. Heatmaps showing missing value distributions per set and replciate Figure 2. Heatmaps showing missing value distributions per set and replicate

The above naPlots show that missing values tend to accumulate at the end of the gradient, and more specifically in the first few fractions of the gradient of each experiment, which classically reflect the gradient distribution e.g. PSMs that are often highly mitochondrial have a huge signal in the heavy channels but little elsewhere in the gradient and thus can result in missing values in the other channels. It is also common to find biologically relevant missing values such as those resulting from the absence of the low abundance of ions. As values do not appear to be missing at random we use a left-censored deterministic minimal value approach, MinDet in MSnbase.

psms.imputed <- lapply(psms, function(z) impute(z, method = "MinDet"))

PSMs were quality controlled post-imputation and then combined to protein level by calculating the median of all PSM intensities corresponding to the leading Uniprot Accession number for each protein group.

4.2.2 Data normalisation

Following the standard pRoloc workflow8 PSMs are scaled into the same intensity interval by dividing each intensity by the sum of the intensities for that quantitative feature. This transformation of the data assures cancellation of the effect of the absolute intensities of the quantitative features along the rows, and focuses subsequent analyses on the relative profiles along the sub-cellular channels. This is important for spatial proteomic experiments are proteins that co-localise in a cell are known to exhibit similar quantitative profiles across the gradient fractions employed.9

psms.imputed.norm <- lapply(psms.imputed, function(z) normalise(z,  "sum"))

4.2.3 Aggregation to protein

We use all available PSMs for quantification and aggregate PSMs to proteins using the combineFeatures function in MSnbase by calculating the median of all PSM intensities corresponding to the leading Uniprot Accession number for each protein group.

prots <- lapply(psms.imputed.norm, function(z) 
  combineFeatures(z, method = "median",
                  groupBy = fData(z)[, "Master.Protein.Accessions"]))

## become combining all protein sets we tidy up the feature labels of the dataset
ll <- paste0(rep(c("unst", "lps"), 1, each = 6), ".r",
             rep(1:3, 1, each = 2), ".s", 1:2)
for (i in seq(prots)) {
  prots@x[[i]] <- updateFvarLabels(prots[[i]], label = ll[i], sep = "_")
}

## combine TMT sets and examine each replicate for each condition
unst_r1 <- filterNA(MSnbase::combine(prots[[1]], prots[[2]]))
unst_r2 <- filterNA(MSnbase::combine(prots[[3]], prots[[4]]))
unst_r3 <- filterNA(MSnbase::combine(prots[[5]], prots[[6]]))
lps_r1 <- filterNA(MSnbase::combine(prots[[7]], prots[[8]]))
lps_r2 <- filterNA(MSnbase::combine(prots[[9]], prots[[10]]))
lps_r3 <- filterNA(MSnbase::combine(prots[[11]], prots[[12]]))


tot_prots <- data.frame("Unstimulated" = c(`Replicate 1` = nrow(unst_r1), 
                                           `Replicate 2` = nrow(unst_r2), 
                                           `Replicate 3`= nrow(unst_r3)),
                        "LPS" = c(`Replicate 1` = nrow(lps_r1), 
                                                 `Replicate 2` = nrow(lps_r2), 
                                                 `Replicate 3` = nrow(lps_r3)))
library("knitr")
library("kableExtra")
kable(tot_prots, caption = "Number of quantified proteins per replicate per condition") %>%
  kable_minimal()
Table 4.1: Number of quantified proteins per replicate per condition
Unstimulated LPS
Replicate 1 5107 4879
Replicate 2 4838 4866
Replicate 3 5733 5848

4.3 Protein level data processing

As shown in the above section we quantify between ~4800-5800 proteins per replicate. The below Venn diagrams show the overlap between replicates within conditions. We find 3882 proteins common in the unstimulated hyperLOPIT experiment and 4067 in the 12h-LPS stimulated hyperLOPIT experiment.

## Load R packages required for genetrating Venn diagrams
library("VennDiagram")
library("scales")
library("ggplot2")

venn.diagram(
  x = list(featureNames(unst_r1), featureNames(unst_r2), featureNames(unst_r3)),
  category.names = c("Replicate 1" , "Replicate 2 " , "Replicate 3"),
  filename = "venn_unst_replicates.png",
  main = "Unstimulated hyperLOPIT",
  col=c("#440154ff", '#21908dff', '#fde725ff'),
  fill = c(alpha("#440154ff",0.3), alpha('#21908dff',0.3), alpha('#fde725ff',0.3)),
  fontfamily = "sans",
  cat.fontfamily = "sans",
  main.fontfamily = "sans",
  cat.col = c("#440154ff", '#21908dff', '#fde725ff'), 
  output = FALSE
)

venn.diagram(
  x = list(featureNames(lps_r1), featureNames(lps_r2), featureNames(lps_r3)),
  category.names = c("Replicate 1" , "Replicate 2 " , "Replicate 3"),
  filename = "venn_lps_replicates.png",
  main = "LPS stimulated hyperLOPIT 12h-LPS",
  col=c("#440154ff", '#21908dff', '#fde725ff'),
  fill = c(alpha("#440154ff",0.3), alpha('#21908dff',0.3), alpha('#fde725ff',0.3)),
  fontfamily = "sans",
  cat.fontfamily = "sans",
  main.fontfamily = "sans",
  cat.col = c("#440154ff", '#21908dff', '#fde725ff')
)
Overlap of protein groups identified between replicated experiments

Figure 4.1: Overlap of protein groups identified between replicated experiments

(Venn diagrams as per supplementary figure 1 of [1])

4.4 Dataset concatenation

We use the combine function in MSnbase to perform data fusion (concatenation) within each condition to maximise subcellular resolution and thus the reliability in protein classification [as per the pRoloc pipeline.6 Trotter et al. 10 have shown a significant improvement in classifier accuracy when combining replicated experiments in spatial proteomics. Specifically, we concatenate all gradient fractions across replicated experiments to give better discrimination between subcellular niches (as shown in6) which gives users the opportunity to resolve niches that may not have been discovered when examining replicates alone.

We note (as explained in8) that direct comparisons of individual TMT channels in replicated experiments do not provide an adequate, goal-driven assessment of different experiments due to the nature of the gradient fraction collection, where quantitative channels do not correspond to identical selected fractions along the gradient.

Concatenate replicates for each condition,

## combine replicates for each condition
unst_full <- filterNA(MSnbase::combine(unst_r1, unst_r2))
unst_full <- filterNA(MSnbase::combine(unst_full, unst_r3)) 

lps_full <- filterNA(MSnbase::combine(lps_r1, lps_r2))
lps_full <- filterNA(MSnbase::combine(lps_full, lps_r3)) 
venn.diagram(
  x = list(featureNames(unst_full), featureNames(lps_full)),
  category.names = c("Unstimulated" , "LPS-stimulated"),
  filename = "venn_hyperLOPIT.png",
  main = "Subset of common proteins between hyperLOPIT conditions",
  col=c("#440154ff", '#21908dff'),
  fill = c(alpha("#440154ff",0.3), alpha('#21908dff',0.3)),
  fontfamily = "sans",
  cat.fontfamily = "sans",
  main.fontfamily = "sans",
  cat.col = c("#440154ff", '#21908dff'),
  margin = 0.05,
  cat.pos = c(-140, 140),
  cat.dist = c(0.05, 0.05)
)

Figure 4. Overlap of protein groups identified in both conditions

We find 3288 proteins common all 12 experiments. In the next code chunk we subset our data to keep only proteins common between all experiments.

4.4.1 Final hyperLOPIT datasets for downstream analysis

Subset for common proteins in all replicates and all conditions,

## keep only those in common between conditions
cmn <- intersect(featureNames(unst_full), featureNames(lps_full))
lps <- lps_full[cmn, ]
unst <- unst_full[cmn, ]

4.4.2 Adding marker proteins

A list of 783 annotated, unambiguous resident organelle marker proteins from 11 subcellular niches: mitochondria, ER, Golgi apparatus, lysosome, peroxisome, PM, nucleus, nucleolus, chromatin, ribosome and cytosol, were curated from The Uniprot database, Gene Ontology11 and from careful mining of the literature. Only proteins known to localise to a single location were included as markers.

We use the addMarkers function in pRoloc to annotate our two datasets from this list of markers (found in the csv directory of this repository). The marker list can also be extracted from pRolocdata by using the getMarkers function with any of the THP datasets.

Adding markers,

lps <- addMarkers(lps, markers = "csv/markers_2019.28.01.csv")
## Markers in data: 783 out of 3288
## organelleMarkers
##      40S/60S Ribosome             Chromatin               Cytosol 
##                    75                    73                    52 
## Endoplasmic Reticulum       Golgi Apparatus              Lysosome 
##                    69                    39                    49 
##          Mitochondria             Nucleolus               Nucleus 
##                   197                    38                    91 
##            Peroxisome       Plasma Membrane               unknown 
##                    29                    71                  2505
unst <- addMarkers(unst, markers = "csv/markers_2019.28.01.csv")
## Markers in data: 783 out of 3288
## organelleMarkers
##      40S/60S Ribosome             Chromatin               Cytosol 
##                    75                    73                    52 
## Endoplasmic Reticulum       Golgi Apparatus              Lysosome 
##                    69                    39                    49 
##          Mitochondria             Nucleolus               Nucleus 
##                   197                    38                    91 
##            Peroxisome       Plasma Membrane               unknown 
##                    29                    71                  2505

4.4.3 Visualising the data

We use t-Distributed Stochastic Neighbor Embedding (t-SNE) to project our 60 dimension data into two-dimensions to visualise organelle separation.

In the below code chunk we plot the data and highlight the subcellular markers on each dataset (Figure 4 of2). Each point represents one protein. Markers are highlighted by different colours as denoted by the key and proteins for the subcellular location is unknown or undefined are grey.

source("r/prettymap.R")
## set organelle colours
mycol <- c("#88CCEE", "#332288", "#53CAB7", "#0170b4", "#204f20", "#990000",
           "#E69F00", "#DDCC77", "#E18493", "#AA4499", "#D55E00")
setStockcol(mycol)
setUnknownpch(16)


## Generate the t-SNE coords (Figure 4 of [1])
set.seed(399)
tsne_unst <- plot2D(unst, method = "t-SNE", plot = FALSE)
set.seed(399)
tsne_lps <- plot2D(lps, method = "t-SNE", plot = FALSE)


## plot the data as t-SNE maps
par(mfrow = c(1, 2))        
oo = c(1:3, 10, 5, 4, 6:7, 9, 8, 11)
myleg <- c(getMarkerClasses(unst), "Unknown")

prettyTSNE(tsne_unst, unst, fcol = "markers", 
          main = "Subcellular markers\nunstimulated THP1-cells",
          orgOrder = oo, mainCol = mycol, oulineCol = darken(mycol))
prettyTSNE(tsne_lps, lps, 
          main = "Subcellular markers\n12h-LPS stimulated THP1-cells",
          orgOrder = oo, mainCol = mycol, oulineCol = darken(mycol))

## add a legend
plot(NULL, xaxt='n',yaxt='n',bty='n',ylab='',xlab='', xlim=0:1, ylim=0:1)
legend("topleft", legend = myleg, col = mycol, bty = "n",
       pch = 19, cex = 1.2, pt.cex = 1.6)

Figure 5. t-SNE plots of the (concatenated) LOPIT datasets for each condition (Figure 4 of [1])

4.5 Classification using a Bayesian framework

Proteins are assigned to one of the 11 subcellular marker classes using TAGM-MCMC.12 TAGM-MCMC is a semi-supervised Bayesian generative classifier based on a T-Augmented Gaussian Mixture model that uses Bayesian computation performed using Markov-chain Monte Carlo. It is one of the many supervised machine learning methods available in the pRoloc package, but currently the only Bayesian method that is available in the package for the prediction of subcellular location for spatial proteomics data.

The advantage of using TAGM, and Bayesian methods in general, over classic supervised machine learning approaches is the ability for the algorithm to quantify the localisation uncertainty. The TAGM classifier models each annotated subcellular class using a Gaussian distribution and thus the full dataset can be modelled as a mixture of Gaussians. As described in12 an outlier component probability is also included in this model which is mathematically described as a multivariate student’s t-distribution which gives extra information regarding the

Full details of the TAGM Bayesian methodology and mathematics are found in12 and a detailed Bioconductor workflow for Bayesian analysis of spatial proteomoics data can be found in.13 We follow this workflow for the analysis used here.

4.5.1 Training

Following Crook et al13 we take the concatenated datasets for each conditions and run the TAGM-MCMC workflow. A collapsed Gibbs sampler was run in parallel for 9 chains to sample from the posterior distributions of localisation probabilites, with each chain run for 25,000 iterations and the Gelman-Rubin’s diagnostic was used to assess the convergence of the 9 Markov-Chains and the 3 best chains were kept and pooled for data processing for each condition.

In the code chunk below we show how to run the tagmMcmcTrain function to generate the samples from the posterior distributions of each dataset (note: we do not run this dynamically as running is computationally intensive. We suggest you use a cluster or HPC if you have one available and scale according to the cores and numChains used)

## Load the coda library for calculating the Gelman  diagnostics
library("coda")

## Train TAGM-MCMC
p_lps <- tagmMcmcTrain(object = lps,
                       numIter = 25000,
                       burnin = 5000,
                       thin = 10,
                       numChains = 9)

p_unst <- tagmMcmcTrain(object = unst,
                        numIter = 25000,
                        burnin = 5000,
                        thin = 10,
                        numChains = 9)

## All chains look good and all oscillate around an average of 490 outliers
out_unst <- mcmc_get_outliers(p_unst)
out_lps <- mcmc_get_outliers(p_lps)

## Calculate Gelman's Diagnostic
gelman.diag(out_unst)   
gelman.diag(out_lps)

# check all pairs as per the f1000 vignette [10]
gelman.diag(out_unst[1:2])
gelman.diag(out_unst[1:3]) # etc...

Following the TAGM workflow13 we examine the chains for convergence and indeed find all chains look good and oscillate around and average of 490 outliers. We calculate the Gelman and Rubin Diagnostic14 for a more rigorous and unbiased analysis of convergence. This statistic is often referred to as R̂ or the potential scale reduction factor and Geman and Rubin suggest that chains with a R̂ < 1.2 are likely to have converged. We find all to be < 1.2. We pick the 5 best chains for each dataset which yields the lowest upper confidence interval (as per12). These are then passed to mcmc_pool_chains and tagmMcmcProcess where the the summary slot of the MCMCParams object is populated with basic summaries of the MCMCChains, such as allocations and localisation probabilities.

mcmc_unst_pooled <- mcmc_pool_chains(mcmc_unst)
mcmc_lps_pooled <- mcmc_pool_chains(mcmc_lps)

mcmc_unst_pooled <- tagmMcmcProcess(mcmc_unst_pooled)
mcmc_lps_pooled <- tagmMcmcProcess(mcmc_lps_pooled)

summary(mcmc_unst_pooled@summary@posteriorEstimates)
summary(mcmc_unst_pooled@summary@tagm.joint)

summary(mcmc_lps_pooled@summary@posteriorEstimates)
summary(mcmc_lps_pooled@summary@tagm.joint)

mcmc_lps <- mcmc_lps_pooled
mcmc_unst <- mcmc_unst_pooled

4.5.2 Prediction

Finally, we use the tagmMcmcPredict function to obtain the full probability distribution over all proteins.

unst <- tagmMcmcPredict(unst, params = mcmc_unst_pooled, probJoint = TRUE)
lps <- tagmMcmcPredict(lps, params = mcmc_lps_pooled, probJoint = TRUE)

The TAGM results are appended to the fData slot of the datasets. As the above chunk is not run dynamically here we can load the results from the pRolocdata package (and they can also be found in Supplementary Table 6 of2). We load the datasets called thpLOPIT_unstimulated_mulvey2021 and thpLOPIT_lps_mulvey2021 which contain all machine learning results from.2 As per the above the analysis this was conducted on proteins common in all experiments.

## Load the data from pRolocdata
data("thpLOPIT_unstimulated_mulvey2021")
data("thpLOPIT_lps_mulvey2021")

## We subset for proteins common in all experiments
cmn <- intersect(featureNames(thpLOPIT_unstimulated_mulvey2021),
                 featureNames(thpLOPIT_lps_mulvey2021))
unst <- thpLOPIT_unstimulated_mulvey2021[cmn, ]
lps <- thpLOPIT_lps_mulvey2021[cmn, ]


## Generate the t-SNE coords 
set.seed(399)
tsne_unst <- plot2D(unst, method = "t-SNE", plot = FALSE)
set.seed(399)
tsne_lps <- plot2D(lps, method = "t-SNE", plot = FALSE)

## plot TAGM assignments
par(mfrow = c(1, 2))
mycol <- paste0(getStockcol())
oo <- c(1:3, 10, 5, 4, 6:7, 9, 8, 11)
myleg <- c(getMarkerClasses(unst), "Unknown")

prettyTSNE(tsne_unst, unst, fcol = "tagm.mcmc.allocation", 
          main = "TAGM-MCMC allocation\nunstimulated THP1-cells",
          orgOrder = oo, mainCol = mycol, oulineCol = darken(mycol))
prettyTSNE(tsne_lps, lps, fcol = "tagm.mcmc.allocation",
          main = "TAGM-MCMC allocation\n12h-LPS stimulated THP1-cells",
          orgOrder = oo, mainCol = mycol, oulineCol = darken(mycol))

plot(NULL, xaxt='n',yaxt='n',bty='n',ylab='',xlab='', xlim=0:1, ylim=0:1)
legend("topleft", legend = myleg, col = mycol, bty = "n",
       pch = 19, cex = 1.2, pt.cex = 1.6)

Figure 6. t-SNE plots of the TAGM output on the (concatenated) LOPIT datasets for each condition

The results from the TAGM-MCMC method are appended to the fData of the MSnSet e.g. see fData(thpLOPIT_unstimulated_mulvey2021)$tagm.mcmc.allocation and fvarLabels(thpLOPIT_unstimulated_mulvey2021) to see all available slots.

Relevent to this analysis we examine the following output slots - tagm.mcmc.allocation: the TAGM-MCMC predictions for the most probable protein sub-cellular allocation. - tagm.mcmc.probability: the mean posterior probability for the protein sub-cellular allocations - tagm.mcmc.outlier: the posterior probability for the protein to belong to the outlier component. - tagm.mcmc.probability.lowerquantile and tagm.mcmc.probability.upperquantile are the lower and upper boundaries to the equi-tailed 95% credible interval of tagm.mcmc.probability.

4.5.3 Thresholding

As with all supervised learning algorithms the classifier is only able to predict a location to one of the classes that are found in the training set. Our training set contained 11 subcellular niches, as described above. It is common practice in supervised machine learning to set a specific score cutoff/threshold on which to define new assignments/allocations, below which classifications are left unassigned/unknown if we do not expect all examples (proteins) to fall into one of the categories in the discrete training set. Indeed, we do not expect the whole subcellular diversity to be represented by the 11 niches used here, we expect there to be many more, many of which will be multiply localised within the cell. It is important to allow for the possibility of proteins to fall reside in multiple locations (see12 for more details).

One advantage of using a Bayesian framework is the outputs of the classifier are probabilities. This not only allows us to look at the distribution of probabilities over all subcellular classes but also allows us to extract a probability score threshold from the product of the posterior and outlier probabilities, on which we can define new assignments, we call this our localisation probability.

   localisation probability = posterior probability * outlier probability  

The localisation probability is calculated for all proteins in each dataset and appended to the fData slot of each dataset.

fData(unst)$localisation.prob <- fData(unst)$tagm.mcmc.probability * (1 - fData(unst)$tagm.mcmc.outlier)
fData(lps)$localisation.prob <- fData(lps)$tagm.mcmc.probability * (1 - fData(lps)$tagm.mcmc.outlier)

As described in the methods of [1] and as demonstrated in the code chunk below, we take a conservative approach, only assign proteins to a subcellular niche if the posterior probability was greater than 0.99 and if the outlier probability was very small < 1e-6, else proteins were left unassigned and labelled as proteins of “unknown location” in the data.

Our assignment threshold which we apply to the localisation probability is therefore 0.99 * 1e-6 = 0.99. We use the getPredictions function on the calculated localisation.prob to extract these newly assigned proteins.

## localisation probability = posterior probability * outlier probability
fData(unst)$localisation.prob <- fData(unst)$tagm.mcmc.probability * (1 - fData(unst)$tagm.mcmc.outlier)
fData(lps)$localisation.prob <- fData(lps)$tagm.mcmc.probability * (1 - fData(lps)$tagm.mcmc.outlier)

## Threshold = (posterior > 0.99) * 1 - (outlier < 1e-6)
t_loc <- 0.99 * (1 - 1e-6)
unst <- getPredictions(unst, 
                       fcol = "tagm.mcmc.allocation", 
                       scol = "localisation.prob", 
                       t = t_loc)
## ans
##      40S/60S Ribosome             Chromatin               Cytosol 
##                    83                   153                   574 
## Endoplasmic Reticulum       Golgi Apparatus              Lysosome 
##                   352                    47                   261 
##          Mitochondria             Nucleolus               Nucleus 
##                   477                    39                   315 
##            Peroxisome       Plasma Membrane               unknown 
##                    34                   165                   788
lps <- getPredictions(lps, 
                      fcol = "tagm.mcmc.allocation", 
                      scol = "localisation.prob", 
                      t = t_loc)
## ans
##      40S/60S Ribosome             Chromatin               Cytosol 
##                    91                   103                   440 
## Endoplasmic Reticulum       Golgi Apparatus              Lysosome 
##                   387                    43                   266 
##          Mitochondria             Nucleolus               Nucleus 
##                   475                    39                   385 
##            Peroxisome       Plasma Membrane               unknown 
##                    40                   227                   792

We can visualise these assignments on a t-SNE plot and also plot the quantitation profiles for each subcellular class.

## plot TAGM assignments
par(mfrow = c(1, 2))
mycol <- paste0(getStockcol())
oo = c(1:3, 10, 5, 4, 6:7, 9, 8, 11)
myleg <- c(getMarkerClasses(unst), "Unknown")

prettyTSNE(tsne_unst, unst, fcol = "tagm.mcmc.allocation.pred", 
          main = "TAGM-MCMC final assignment\nunstimulated THP1-cells",
          orgOrder = oo, mainCol = mycol, oulineCol = darken(mycol))
## Warning in plot.window(...): "oulineCol" is not a graphical parameter
## Warning in plot.xy(xy, type, ...): "oulineCol" is not a graphical parameter
## Warning in axis(side = side, at = at, labels = labels, ...): "oulineCol" is not
## a graphical parameter

## Warning in axis(side = side, at = at, labels = labels, ...): "oulineCol" is not
## a graphical parameter
## Warning in box(...): "oulineCol" is not a graphical parameter
## Warning in title(...): "oulineCol" is not a graphical parameter
prettyTSNE(tsne_lps, lps, fcol = "tagm.mcmc.allocation.pred",
          main = "TAGM-MCMC final assignment\n12h-LPS stimulated THP1-cells",
          orgOrder = oo, mainCol = mycol, oulineCol = darken(mycol))
## Warning in plot.window(...): "oulineCol" is not a graphical parameter
## Warning in plot.xy(xy, type, ...): "oulineCol" is not a graphical parameter
## Warning in axis(side = side, at = at, labels = labels, ...): "oulineCol" is not
## a graphical parameter

## Warning in axis(side = side, at = at, labels = labels, ...): "oulineCol" is not
## a graphical parameter
## Warning in box(...): "oulineCol" is not a graphical parameter
## Warning in title(...): "oulineCol" is not a graphical parameter

plot(NULL, xaxt='n',yaxt='n',bty='n',ylab='',xlab='', xlim=0:1, ylim=0:1)
legend("topleft", legend = myleg, col = mycol, bty = "n",
       pch = 19, cex = 1.2, pt.cex = 1.6)

Figure 7. t-SNE plots showing the final subcellular assignments of the (concatenated) LOPIT datasets for each condition (Figure 4 of [1])

Not including the 783 marker proteins, after classification and thresholding we find 1717 and 1713 proteins to localise to one of the 11 subcellular niches in the data for the unstimulated and 12h-LPS stimulated datasets respectively.

## Unstimulated protein profiles
par(mfrow = c(4, 3))
cl <- getMarkerClasses(unst)
for (i in seq(cl)) {
  par(mar = c(8, 4, 8, 2), las = 2)
  plotDist(unst[ fData(unst)$markers == getMarkerClasses(unst)[i], ], 
           pcol = paste0(getStockcol()[i], 90), xlab = "", ylim = c(0, .9))
  title(main = cl[i])
}
Class-specific protein (unstimulated THP1-cell experiments)

Figure 8. Protein quantitation profiles for proteins in the unstimulated experiments grouped by subcellular localisation

## LPS protein profiles
par(mfrow = c(4, 3))
cl <- getMarkerClasses(lps)
for (i in seq(cl)) {
  par(mar = c(8, 4, 8, 2), las = 2)
  plotDist(lps[ fData(lps)$markers == getMarkerClasses(lps)[i], ], 
           pcol = paste0(getStockcol()[i], 90), xlab = "", ylim = c(0, .9))
  title(main = cl[i])
}
Class-specific protein profiles (12h-LPS stimulated THP1-cell experiments)

Figure 9. Protein quantitation profiles for proteins in the 12h-LPS stimulated experiments grouped by subcellular localisation

4.5.4 Overlap in protein locations

The code chunk below shows a summary of of the protein residency in each condition for the 3288 proteins common in all experiments.

loc_unst <- getMarkers(unst, "tagm.mcmc.allocation.pred")
## organelleMarkers
##      40S/60S Ribosome             Chromatin               Cytosol 
##                    83                   153                   574 
## Endoplasmic Reticulum       Golgi Apparatus              Lysosome 
##                   352                    47                   261 
##          Mitochondria             Nucleolus               Nucleus 
##                   477                    39                   315 
##            Peroxisome       Plasma Membrane               unknown 
##                    34                   165                   788
loc_lps <- getMarkers(lps, "tagm.mcmc.allocation.pred")
## organelleMarkers
##      40S/60S Ribosome             Chromatin               Cytosol 
##                    91                   103                   440 
## Endoplasmic Reticulum       Golgi Apparatus              Lysosome 
##                   387                    43                   266 
##          Mitochondria             Nucleolus               Nucleus 
##                   475                    39                   385 
##            Peroxisome       Plasma Membrane               unknown 
##                    40                   227                   792
kable(rbind("Unstimulated" = table(loc_unst), "LPS" = table(loc_lps)))
40S/60S Ribosome Chromatin Cytosol Endoplasmic Reticulum Golgi Apparatus Lysosome Mitochondria Nucleolus Nucleus Peroxisome Plasma Membrane unknown
Unstimulated 83 153 574 352 47 261 477 39 315 34 165 788
LPS 91 103 440 387 43 266 475 39 385 40 227 792

The heatmap below shows the sub-cellular distribution of proteins between the two conditions including the 783 annotated marker proteins (Figure 4A, Supplementary Table 7 of2). Using TAGM a additional 1,717 proteins in the unstimulated dataset and 1,713 proteins in the LPS dataset were classified to distinct subcellular regions with high confidence. The remaining proteins that did not meet the classifier threshold were left unassigned and labelled as “unknown”. As already mentioned above we do not expect all proteins to localise to one main discrete location, many proteins are known to “moonlight”15 and live in mixed locations. There was however a high degree of correlation between unstimulated and stimulated datasets, with 61% of the identified proteome sharing the same organelle localisation in both conditions (75% including the marker proteins).

source("R/intersection.R")
## Add a label to the LPS dataset so when we combine it  
## with the unstimulated the information is not lost
.lps <- lps
.lps <- updateFvarLabels(.lps, "LPS")
cmb <- MSnbase::combine(unst, .lps)  # now combine all results

df_all <- compareDatasets(cmb, 
                          fcol1 = "localisation.pred",
                          fcol2 = "localisation.pred.LPS")
ggheatmap(df_all, title = "TAGM locations between conditions")

Figure 10. Heatmap showing the distribution and overlap in organelle assignments between the TAGM classifications in the unstimulated data and classifications in the LPS-stimulated data (Figure S1,G of [1])

4.6 Translocations

One of the main aims of the THP-1 study in2 was to use the hyperLOPIT technology to investigate the (spatial) changes in protein localisation between the unstimulated and 12h-LPS stimulated conditions. In this section we look at how the assigned location of proteins differ between the two conditions. We examine their assigned location from TAGM and also the difference between their joint posterior probability distributions.

Re-localisations i.e. proteins that do not localise to the same subcellular niche in both conditions, were extracted and categorised as one of four different translocation events:

  1. Type 1: organelle-to-organelle - cases where proteins are assigned to different subcellular classes in each condition i.e. those that move from one discrete location to another.
  2. Type 2: unknown-to-organelle - cases where proteins in the unstimulated data are labelled as “unknown” but are localised to one of the 11 organelle classes in the 12h-LPS stimulated data.
  3. Type 3: organelle-to-unknown - cases where proteins are assigned a location to one of the 11 organelle classes in the unstimulated data but in the 12h-LPS data are labelled as “unknown”.
  4. Type 4: unknown-to-unknown - dynamic proteins that were not assigned a subcellular niche. These are proteins which have a difference in their joint probability distribution equal to 1 (as calculated by the natural L2 distance, also known as the Euclidean norm).

The L2 distance is denoted by \[ d_{_{L2}norm}(x, y) = \sqrt {\sum_{i = 1}^{n}{(x_i - y_i)^2}} \] where x and y are the posterior probabilities for for the unstimulated and 12h-LPS stimulated respectively, for each ith class.

As already mentioned, proteins labelled as “unknown” location, describe proteins that did not meet the classifier threshold to be assigned to one of the 11 discrete organelle categories, as defined in our marker set. We not only wish to examine proteins that move between discrete locations (which we call type 1 translocations above) but we also wish to examine cases where in one condition the protein has been given a discrete location, and in another condition it has been described as “unknown”, and therefore not belonging to one of the 11 subcellular classes. It is important to note that in terms of machine learning, the term “unknown” is not a class in the training set, it is simply as term we use in our pipeline to describe proteins that we can say as not confidently assigned to one of the 11 classes.

These cases are still of great interest as although we may not know where the protein exactly resides in this snapshot of time we do know it is not classed to the same location in the other condition. Unknown cases encompass a number of scenarios for example, a protein having a mixed population and moonlighting between organelles, or it be that the protein localised to a subcellular niche that does not appear in our training data. Whichever reason what we are interested in is not only a change in localion but the size of the probability change, as this implies a change in localisation regardless of organelle residency.

As an extra measure of stringency we enforce that in cases 2-4 any protein considered as “unknown” must also have a high outlier probability (as per the below code chunk out = 1e-3) and thus is clearly not a member of any of the 11 classes.

For every protein the natural L2 distance (Euclidean norm) was calculated between the TAGM joint posterior probability distributions and translocation events were further filtered and ranked based on this distance to give a succinct subset of translocations for further analysis and data mining.

In the below code chunk we call the function getTranslocations which extracts the localisations of every protein in each condition then assigns a translocation type as described above and then calculates the L2 distance between the posterior probability distributions. This information is appended to the fData slot of our MSnSet along with all the other machine learning results.

source("R/getmovers.R")
extract_movers <- getTranslocations(unst, lps, 
                                    fcol = "tagm.mcmc.allocation.pred", 
                                    out = 1e-3)
unst <- extract_movers[[1]]
lps <- extract_movers[[2]]

getMarkers(unst, "translocations")
## organelleMarkers
## type1 type2 type3 type4 
##   112    62    49    30

In summary classed by translocation events we find - - 112 type 1 - 62 type 2 - 49 type 3 - 30 type 4 proteins that move following 12 hour stimulation with LPS.

In the next section we can further examine and plot these events using alluvial and circos plots.

4.6.1 Visualisation of translocations

The following code chunks use functions from the circlize and ggalluvial packages (full source code in this repo and at the end of this vignette) to generate circos and alluvial plots respectively. These plots summarise the flow of proteins between organelle locations and the two conditions.

4.6.1.1 Circos plot of protein translocations across organelles

source("R/circos.R")

## generate my colour scheme
circos_cols <- c(getStockcol(), "grey")
orgs <- c(union(getMarkerClasses(unst), getMarkerClasses(lps)), "unknown")
(colscheme <- setNames(circos_cols, orgs))  # check levels consistent 
##      40S/60S Ribosome             Chromatin               Cytosol 
##             "#88CCEE"             "#332288"             "#53CAB7" 
## Endoplasmic Reticulum       Golgi Apparatus              Lysosome 
##             "#0170b4"             "#204f20"             "#990000" 
##          Mitochondria             Nucleolus               Nucleus 
##             "#E69F00"             "#DDCC77"             "#E18493" 
##            Peroxisome       Plasma Membrane               unknown 
##             "#AA4499"             "#D55E00"                "grey"
## get indices of translocating proteins
tl <- !is.na(fData(unst)$translocations)

## set circos plot parameters
par(mar = c(2, 2, 2, 2), cex = .5)
circos.clear()
circos.par(gap.degree = 4)

## plot circos
customChord(unst[tl, ], lps[tl, ], 
            cols = colscheme,  
            diffHeight  = -0.02, 
            transparency = 0.3, 
            link.sort = FALSE,
            labels = TRUE)

Note: labels were optimised in Inkscape Figure 11. Directional circos plot showing protein translocations (Fig. S4A of Mulvey et al 2021

4.6.1.2 Alluvial (river) plot of protein translocations across organelles

source("r/riverplot.R")
library(ggalluvial)
thp_alluvial <- riverplot(unst[tl, ], lps[tl, ], 
                          cols = colscheme,
                          labels = TRUE)
thp_alluvial + ggtitle("Translocations following 12h-LPS stimulation in THP-1 cells") 

Note: labels were optimised in Inkscape

Figure 12. Alluvial (river) plot showing the 253 protein translocations (Fig. 4C of Mulvey et al. 2021

Alluvial (river) plot showing the 253 protein translocations (Figure 4C of2) proteins which were found to move between organelles at 12h-LPS. The number of proteins that are found to translocate to and from each subcellular compartment is denoted next to the labelled alluvial blocks

4.6.2 Spatial map of translocating proteins

The t-SNE maps below also show the visual localisation of translocating proteins.

source("r/foi.R")
par(mfrow = c(1, 2))

## Unstimulated
prettyTSNE_overlay(tsne_unst, unst, 
                   fcol = "localisation.pred", 
                   main = "Type 1, 2, 3, or 4 translocation events", orgOrder =  oo, 
                   mainCol = paste0(lighten(mycol), 30),
                   oulineCol = paste0(lighten(mycol), 30))
highlightOnPlot(object = tsne_unst, pch = 21, cex = 1.7,
                col = mycol[2],  bg = lighten(lighten("grey")),
                foi = type1, lwd = 2,
                args = list(unst))
highlightOnPlot(object = tsne_unst, pch = 24, cex = 1.7,
                col = darken(mycol[7]), bg = lighten(lighten("#f4f4c8")), 
                foi = type2, lwd = 2, 
                args = list(unst))
highlightOnPlot(object = tsne_unst, pch = 21, cex = 1.7,
                col = darken("purple"), bg = lighten(lighten("#f2a6e3")), 
                foi = type3, lwd = 2,
                args = list(unst))
highlightOnPlot(object = tsne_unst, pch = 24, cex = 1.7,
                col = "#1c1c78", bg = lighten(lighten("#5fa8cc")), 
                foi = type4, lwd = 2,
                args = list(unst))

prettyTSNE_overlay(tsne_lps, lps, fcol = "localisation.pred", 
                   main = "", orgOrder =  oo,
                   mainCol = paste0(lighten(mycol), 30), 
                   oulineCol = paste0(lighten(mycol), 30))
## 12h-LPS 
highlightOnPlot(object = tsne_lps, pch = 21, cex = 1.7,
                col = mycol[2],  bg = lighten(lighten("grey")),
                foi = type1, lwd = 2,
                args = list(lps))
highlightOnPlot(object = tsne_lps, pch = 24, cex = 1.7,
                col = darken(mycol[7]), bg = lighten(lighten("#f4f4c8")), 
                foi = type2, lwd = 2, 
                args = list(lps))
highlightOnPlot(object = tsne_lps, pch = 21, cex = 1.7,
                col = darken("purple"), bg = lighten(lighten("#f2a6e3")), 
                foi = type3, lwd = 2,
                args = list(lps))
highlightOnPlot(object = tsne_lps, pch = 24, cex = 1.7,
                col = "#1c1c78", bg = lighten(lighten("#5fa8cc")), 
                foi = type4, lwd = 2,
                args = list(lps))
plot(NULL, xaxt='n',yaxt='n',bty='n',ylab='',xlab='', xlim=0:1, ylim=0:1)
legend("topleft", legend = c("Type 1: organelle-to-organelle", 
                             "Type 2: unknown-to-organelle", 
                             "Type 3: organelle-to-unknown", 
                             "Type 4: unknown-to-unknown"), 
       col = c(mycol[2], darken(mycol[7]), darken("purple"), "#1c1c78"), 
       pt.bg = c(lighten(lighten("grey")), lighten(lighten("#f4f4c8")), 
                 lighten(lighten("#f2a6e3")), lighten(lighten("#5fa8cc"))),
       bty = "n",
       pch = c(21, 24, 21, 24), pt.cex = 2.2, cex = 1.4)

Figure 13. T-SNE dimensionality reduction showing the 253 relocalising proteins from the hyperLOPIT experiments and plotted by Type 1,2,3, or 4 relocalisaton events, which are represented by grey circles, yellow triangles, pink circles and blue triangles, respectively.

4.6.3 Summary table of translocations between conditions

df <- makedf(unst[tl, ], lps[tl, ],
            fcol1 = "localisation.pred",
            fcol2 = "localisation.pred",
            mrkCol1 = "markers",
            mrkCol2 = "markers")
## `summarise()` has grouped output by 'x'. You can override using the `.groups` argument.
df <- df[!df$`Number of translocations`==0, ]
names(df) <- c("Unstimulated", "LPS", "Number of translocations")
kable(df)
Unstimulated LPS Number of translocations

4.7 A scaffold for proteins of interest

Mulvey et al (2021) show that hyperLOPIT provides a means to robustly classify unannotated proteins to distinct organelles and to investigate protein relocalisation events. The following spatial maps as generated in2 highlight interesting findings from the THP-1 hyperLOPIT dataset.

ADD MINI CIRCOS OR RIVER PLOTS FOR ALL OF THESE SPATIAL MAPS?

4.7.1 Relocalisa+on of RHO-GTPase trafficking molecules to the PM

Fig 5

4.7.2 Examples of proteins that exhibit both spatial and temporal regulation

Fig 6

4.7.3 Bayesian temporal clustering analysis

Fig 7

4.7.4 Cytokine proteins

Supp Fig S1

4.7.4.1 Cytosol translocations

4.7.4.2 Nuclear translocations

4.7.4.3 Lysosomal translocations

Supp Fig S2

4.7.4.4 Nucleo-cytoplasmic translocation events

4.7.4.5 Plasma membrane translocation events

Supp Fig S3

4.7.4.6 Trafficking proteins

Supp Fig S4

4.7.4.7 Secretome proteome (Meissner et al., 2013)

4.7.4.8 Cell surface proteome (Kalxdorf et al., 2017)

4.7.4.9 The RNA binding proteome (Liepelt et al., 2016)

4.7.4.10 Mitochondrial and lysosomal proteome (Fu et al., 2020)

Supp Fig S5

5 Figures from Mulvey et al

6 Source code

7 SessionInfo

sessionInfo()
## R version 4.0.5 (2021-03-31)
## Platform: x86_64-apple-darwin17.0 (64-bit)
## Running under: macOS Big Sur 10.16
## 
## Matrix products: default
## BLAS:   /Library/Frameworks/R.framework/Versions/4.0/Resources/lib/libRblas.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/4.0/Resources/lib/libRlapack.dylib
## 
## locale:
## [1] en_GB.UTF-8/en_GB.UTF-8/en_GB.UTF-8/C/en_GB.UTF-8/en_GB.UTF-8
## 
## attached base packages:
## [1] stats4    parallel  stats     graphics  grDevices utils     datasets 
## [8] methods   base     
## 
## other attached packages:
##  [1] ggalluvial_0.12.3    circlize_0.4.12      reshape2_1.4.4      
##  [4] colorspace_2.0-0     kableExtra_1.3.4     knitr_1.33          
##  [7] RColorBrewer_1.1-2   scico_1.2.0          plotrix_3.8-1       
## [10] gplots_3.1.1         gridExtra_2.3        Rmisc_1.5           
## [13] plyr_1.8.6           lattice_0.20-41      tidyr_1.1.3         
## [16] ggrepel_0.9.1        dplyr_1.0.5          ggplot2_3.3.3       
## [19] limma_3.46.0         imputeLCMD_2.0       impute_1.64.0       
## [22] pcaMethods_1.82.0    norm_1.0-9.5         tmvtnorm_1.4-10     
## [25] gmm_1.6-6            sandwich_3.0-0       Matrix_1.3-2        
## [28] mvtnorm_1.1-1        pRolocdata_1.29.1    pRoloc_1.30.0       
## [31] BiocParallel_1.24.1  MLInterfaces_1.70.0  cluster_2.1.1       
## [34] annotate_1.68.0      XML_3.99-0.6         AnnotationDbi_1.52.0
## [37] IRanges_2.24.1       MSnbase_2.16.1       ProtGenerics_1.22.0 
## [40] S4Vectors_0.28.1     mzR_2.24.1           Rcpp_1.0.6          
## [43] Biobase_2.50.0       BiocGenerics_0.36.0 
## 
## loaded via a namespace (and not attached):
##   [1] systemfonts_1.0.1     BiocFileCache_1.14.0  splines_4.0.5        
##   [4] digest_0.6.27         foreach_1.5.1         htmltools_0.5.1.1    
##   [7] viridis_0.6.0         fansi_0.4.2           magrittr_2.0.1       
##  [10] memoise_2.0.0         doParallel_1.0.16     mixtools_1.2.0       
##  [13] recipes_0.1.15        gower_0.2.2           svglite_2.0.0        
##  [16] askpass_1.1           rmdformats_1.0.2      lpSolve_5.6.15       
##  [19] prettyunits_1.1.1     rvest_1.0.0           blob_1.2.1           
##  [22] rappdirs_0.3.3        xfun_0.22             crayon_1.4.1         
##  [25] jsonlite_1.7.2        hexbin_1.28.2         survival_3.2-10      
##  [28] zoo_1.8-9             iterators_1.0.13      glue_1.4.2           
##  [31] gtable_0.3.0          ipred_0.9-11          zlibbioc_1.36.0      
##  [34] webshot_0.5.2         kernlab_0.9-29        shape_1.4.5          
##  [37] scales_1.1.1          vsn_3.58.0            DBI_1.1.1            
##  [40] viridisLite_0.4.0     xtable_1.8-4          progress_1.2.2       
##  [43] bit_4.0.4             proxy_0.4-25          mclust_5.4.7         
##  [46] preprocessCore_1.52.1 lava_1.6.9            prodlim_2019.11.13   
##  [49] sampling_2.9          httr_1.4.2            FNN_1.1.3            
##  [52] ellipsis_0.3.1        farver_2.1.0          pkgconfig_2.0.3      
##  [55] nnet_7.3-15           sass_0.3.1            dbplyr_2.1.1         
##  [58] utf8_1.2.1            caret_6.0-86          labeling_0.4.2       
##  [61] tidyselect_1.1.0      rlang_0.4.10          munsell_0.5.0        
##  [64] tools_4.0.5           LaplacesDemon_16.1.4  cachem_1.0.4         
##  [67] generics_0.1.0        RSQLite_2.2.6         evaluate_0.14        
##  [70] stringr_1.4.0         fastmap_1.1.0         mzID_1.28.0          
##  [73] yaml_2.2.1            ModelMetrics_1.2.2.2  bit64_4.0.5          
##  [76] caTools_1.18.2        purrr_0.3.4           randomForest_4.6-14  
##  [79] dendextend_1.14.0     ncdf4_1.17            nlme_3.1-152         
##  [82] xml2_1.3.2            biomaRt_2.46.3        rstudioapi_0.13      
##  [85] compiler_4.0.5        curl_4.3              e1071_1.7-6          
##  [88] affyio_1.60.0         tibble_3.1.0          bslib_0.2.4          
##  [91] stringi_1.5.3         highr_0.8             vctrs_0.3.7          
##  [94] pillar_1.6.0          lifecycle_1.0.0       BiocManager_1.30.12  
##  [97] GlobalOptions_0.1.2   jquerylib_0.1.3       MALDIquant_1.19.3    
## [100] bitops_1.0-6          data.table_1.14.0     R6_2.5.0             
## [103] affy_1.68.0           bookdown_0.21         KernSmooth_2.23-18   
## [106] codetools_0.2-18      MASS_7.3-53.1         gtools_3.8.2         
## [109] assertthat_0.2.1      openssl_1.4.3         withr_2.4.1          
## [112] hms_1.0.0             grid_4.0.5            rpart_4.1-15         
## [115] timeDate_3043.102     coda_0.19-4           class_7.3-18         
## [118] rmarkdown_2.7         segmented_1.3-3       Rtsne_0.15           
## [121] pROC_1.17.0.1         lubridate_1.7.10

References

1. R Core Team. R: A language and environment for statistical computing. (R Foundation for Statistical Computing, 2017).

2. Mulvey, C. M. et al. Spatiotemporal proteomic profiling of the pro-inflammatory response to lipopolysaccharide in the thp-1 human leukaemia cell line. Nature Communicatio In review, (2021).

3. Gatto, L., Breckels, L. M., Wieczorek, S., Burger, T. & Lilley, K. S. Mass-spectrometry based spatial proteomics data analysis using pRoloc and pRolocdata. Bioinformatics (2014).

4. Perez-Riverol, Y. et al. The pride database and related tools and resources in 2019: Improving support for quantification data. Nucleic Acids Research 47, (2018).

5. Kirk, P., Griffin, J. E., Savage, R. S., Ghahramani, Z. & Wild, D. L. Bayesian correlated clustering to integrate multiple datasets. Bioinformatics 28, 3290–3297 (2012).

6. Christoforou, A. et al. A draft map of the mouse pluripotent stem cell spatial proteome. Nat Commun 7, 9992 (2016).

7. Mulvey, C. M. et al. Using hyperLOPIT to perform high-resolution mapping of the spatial proteome. Nature Protocols 12, 1110–1135 (2017).

8. Breckels, L. M., Mulvey, C. M., Lilley, K. S. & Gatto, L. A bioconductor workflow for processing and analysing spatial proteomics data. F1000Research 5, (2016).

9. De Duve, C. & Beaufay, H. A short history of tissue fractionation. The Journal of cell biology 91, 293 (1981).

10. Trotter, M., Sadowski, P., Dunkley, T., Groen, A. & Lilley, K. Improved sub-cellular resolution via simultaneous analysis of organelle proteomics data across varied experimental conditions. Proteomics 10, 4213–4219 (2010).

11. Ashburner, M. et al. Gene ontology: Tool for the unification of biology. Nature genetics 25, 25–29 (2000).

12. Crook, O. M., Mulvey, C. M., Kirk, P. D. W., Lilley, K. S. & Gatto, L. A bayesian mixture modelling approach for spatial proteomics. PLoS Comput. Biol. 14, e1006516 (2018).

13. Crook, O. M., Breckels, L. M., Lilley, K. S., Kirk, P. D. W. & Gatto, L. A bioconductor workflow for the bayesian analysis of spatial proteomics. F1000Research 8, 446 (2019).

14. Gelman, A. & Rubin, D. B. Inference from iterative simulation using multiple sequences. Statistical Science 7, (1992).

15. Min, K.-W., Lee, S.-H. & Baek, S. J. Moonlighting proteins in cancer. Cancer Letters 370, 108–116 (2016).